深入解析JMM原理

如果大家對java架構相關感興趣,可以關注下面公衆號,會持續更新java基礎面試題, netty, spring boot,spring cloud等系列文章,一系列乾貨隨時送達, 超神之路從此展開, BTAJ不再是夢想!

架構殿堂

概念

​ Java內存模型(Java Memory Model,JMM)JMM主要是爲了規定了線程和內存之間的一些關係。根據JMM的設計,系統存在一個主內存(Main Memory),Java中所有變量都儲存在主存中,對於所有線程都是共享的。每條線程都有自己的工作內存(Working Memory),工作內存中保存的是主存中某些變量的拷貝,線程對所有變量的操作都是在工作內存中進行,線程之間無法相互直接訪問,變量傳遞均需要通過主存完成。

特性

原子性

指一個操作是不可中斷的,即使是多個線程一起執行的時候,一個操作一旦開始,就不會被其他線程干擾

可見性

指當一個線程修改了某一個共享變量的值,其他線程是否能夠立即知道這個修改。顯然,對於串行程序來說,可見性問題 是不存在。因爲你在任何一個操作步驟中修改某個變量,那麼在後續的步驟中,讀取這個變量的值,一定是修改後的新值。但是這個問題在並行程序中就不見得了。如果一個線程修改了某一個全局變量,那麼其他線程未必可以馬上知道這個改動。

有序性

對於一個線程的執行代碼而言,我們總是習慣地認爲代碼的執行時從先往後,依次執行的。這樣的理解也不能說完全錯誤,因爲就一個線程而言,確實會這樣。但是在併發時,程序的執行可能就會出現亂序。給人直觀的感覺就是:寫在前面的代碼,會在後面執行。有序性問題的原因是因爲程序在執行時,可能會進行指令重排,重排後的指令與原指令的順序未必一致

主內存與工作內存

處理器上的寄存器的讀寫的速度比內存快幾個數量級,爲了解決這種速度矛盾,在它們之間加入了高速緩存。

加入高速緩存帶來了一個新的問題:緩存一致性。如果多個緩存共享同一塊主內存區域,那麼多個緩存的數據可能會不一致,需要一些協議來解決這個問題。

在這裏插入圖片描述

所有的變量都 存儲在主內存中,每個線程還有自己的工作內存 ,工作內存存儲在高速緩存或者寄存器中,保存了該線程使用的變量的主內存副本拷貝。

線程只能直接操作工作內存中的變量,不同線程之間的變量值傳遞需要通過主內存來完成。
在這裏插入圖片描述

數據存儲類型以及操作方式

  • 方法中的基本類型本地變量將直接存儲在工作內存的棧幀結構中;
  • 引用類型的本地變量:引用存儲在工作內存,實際存儲在主內存;
  • 成員變量、靜態變量、類信息均會被存儲在主內存中;
  • 主內存共享的方式是線程各拷貝一份數據到工作內存中,操作完成後就刷新到主內存中。

內存間交互

關於主內存與工作內存之間的具體交互協議,即一個變量如何從主內存拷貝到工作內存,如何從工作內存同步回主內存子類的細節實現,java內存模型定義了八種操作:(這八個操作都具有原子性)

lock(鎖定):作用於主內存的變量,把一個變量標識爲一條線程獨佔的狀態。

unclock(解鎖):作用於主內存的變量,把一個處於鎖定的狀態釋放出來。

read(讀取):作用於主內存的變量,把一個變量的值從主內存傳輸到線程的工作內存中

load(載入):作用於工作內存的變量,把read操作從主內存 得到的變量值放入工作內存的變量副本中。

use(使用):作用於工作內存的變量,把工作內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用到變量的                             值的字節碼指令時將會執行這個操作。

assign(賦值):作用於工作內存的變量,把一個從執行引擎接收到的值 賦值給工作內存的變量,每當虛擬機遇到一個給變                             量賦值的字節碼指令時執行這個操作。

store(存儲):作用於工作內存的變量,把工作內存中的一個變量的值傳遞到主內存,以便write操作使用。

write(寫入):作用於主內存的變量,把store操作從工作內存中得到的變量的值放入主內存的變量中。

如圖:

在這裏插入圖片描述

指令重排序的條件

  • 在單線程環境下不能改變程序的運行結果;
  • 存在數據依賴關係的不允許重排序;
  • 無法通過Happens-before原則推到出來的,才能進行指令的重排序。

happens-before 原則

  • 程序次序規則:一個線程內,按照代碼順序,書寫在前面的操作先行發生於書寫在後面的操作
  • 鎖定規則:一個unLock操作先行發生於後面對同一個鎖額lock操作
  • volatile變量規則:對一個變量的寫操作先行發生於後面對這個變量的讀操作
  • 傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C
  • 線程啓動規則:Thread對象的start()方法先行發生於此線程的每個一個動作
  • 線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生
  • 線程終結規則:線程中所有的操作都先行發生於線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行
  • 對象終結規則:一個對象的初始化完成先行發生於他的finalize()方法的開始

jvm和jmm之間的關係

jmm中的主內存、工作內存與jvm中的Java堆、棧、方法區等並不是同一個層次的內存劃分,這兩者基本上是沒有關係的,如果兩者一定要勉強對應起來,那從變量、主內存、工作內存的定義來看,主內存主要對應於Java堆中的對象實例數據部分,而工作內存則對應於虛擬機棧中的部分區域。

從更低層次上說,主內存就直接對應於物理硬件的內存,而爲了獲取更好的運行速度,虛擬機(甚至是硬件系統本身的優化措施)可能會讓工作內存優先存儲於寄存器和高速緩存中,因爲程序運行時主要訪問讀寫的是工作內存。

如果大家對java架構相關感興趣,可以關注下面公衆號,會持續更新java基礎面試題, netty, spring boot,spring cloud等系列文章,一系列乾貨隨時送達, 超神之路從此展開, BTAJ不再是夢想!

架構殿堂

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章