JVM學習筆記七:內存模型JMM


主內存與工作內存

主內存:所有的實例字段、靜態字段和構成數組對象的元素都存儲在主內存,但不包括局部變量與方法參數。

工作內存:每個線程都有自己的工作內存,工作線程中保存了該線程用到的變量的主內存副本拷貝,線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存的變量,不同線程之間也無法直接訪問對方工作內存中的變量,線程間變量值的傳遞均需要通過主內存來完成,線程、主內存、工作內存之間的交互關系圖如下:
jinjiprojectnaotu

內存間交互操作

主內存與工作內存之間具體的交互協議,被定義了以下8種操作來完成,虛擬機實現時必須保證每一種操作都是院子的、不可再分的。

  1. lock,鎖定,所用於主內存變量,它把一個變量標識為一條線程獨占的狀態。
  2. unlock,解鎖,解鎖后的變量才能被其他線程鎖定。
  3. read,讀取,所用於主內存變量,它把一個主內存變量的值,讀取到工作內存中。
  4. load,載入,所用於工作內存變量,它把read讀取的值,放到工作內存的變量副本中。
  5. use,使用,作用於工作內存變量,它把工作內存變量的值傳遞給執行引擎,當JVM遇到一個變量讀取指令就會執行這個操作。
  6. assign,賦值,作用於工作內存變量,它把一個從執行引擎接收到的值賦值給工作內存變量。
  7. store,存儲,作用域工作內存變量,它把工作內存變量值傳送到主內存中。
  8. write,寫入,作用於主內存變量,它把store從工作內存中得到的變量值寫入到主內存變量中。

8種操作的實現規則:

  1. 不允許read和load、store和write操作之一單獨出現,即不允許加載或同步工作到一半。
  2. 不允許一個線程丟棄它最近的assign操作,即變量在工作內存中改變了之后,必須吧改變化同步回主內存。
  3. 不允許一個線程無原因地(無assign操作)把數據從工作內存同步到主內存中。
  4. 一個新的變量只能在主內存中誕生。
  5. 一個變量在同一時刻只允許一條線程對其進行lock操作,但lock操作可以被同一條線程重復執行多次,,多次lock之后必須要執行相同次數的unlock操作,變量才會解鎖。
  6. 如果對一個對象進行lock操作,那會清空工作內存變量中的值,在執行引擎使用這個變量前,需要重新執行load或assign操作初始化變量的值。
  7. 如果一個變量事先沒有被lock,就不允許對它進行unlock操作,也不允許去unlock一個被其他線程鎖住的變量。
  8. 對一個變量執行unlock操作之前,必須將此變量同步回主內存中(執行store、write)。

對於volatile型變量的特殊規則

volatile變量具有兩種特征:1. 是保證此變量對所有線程的可見性;2. 禁止指令重排優化

對於volatile的可見性,在不符合以下兩條規則的運算場景中,仍要枷鎖來保證原子性:

  1. 運算結果並不依賴變量的當前值,或者能夠確保只有單一的線程修改變量的值。
  2. 變量不需要與其他的狀態變量共同參與不變約束。

volatile的實現原理

經過volatile修飾的變量,通過查看JIT編譯后的匯編代碼
jinjiprojectnaotu
可以發現,關鍵在於被volatile修飾的變量,復制后,多執行了一個lock指令,這個操作相當於一個內存屏障,指令重排序時不能把后面的指令重排序到內存屏障之前的位置,add1 0,exp是一個空指令,重點在於lock前綴,它的作用是使得本CPU的Cache寫入了內存,同時其他CPU的Cache失效,這種操作相當於對工作內存變量做了一次store和write,通過這個操作,可以讓volatile修飾的變量對其他CPU可見。

那么它又是如何禁止指令重排的呢?

從硬件架構上講,指令重排序是指CPU采用了允許將多條指令不按程序規定的順序分開發送給各相應電路單頁處理,但並不是說指令任意重排,CPU需要能正確處理指令依賴情況以保證程序能夠得出正確的執行結果,lock指令把修改同步到內存時,意味着所有之前的操作都已經執行完成,這樣便形成了”指令重排無法越過內存屏障“的效果。

java內存模型中對volatile變量定義的特殊規則:

  1. read、load、use 必須一起出現,保證每次使用工作內存中的變量時,都必須從主內存刷新最新的值,以便能看見其他線程對變量所做的修改。
  2. assign、store、write 必須一起出現,保證每次修改都立刻同步到主內存中。
  3. 對兩個不同變量的use或assign操作順序,要和對這兩個變量的read或write操作順序相同(禁止指令重排序)。

對於Long和Double型變量的特殊規則:

對於64位數據類型,虛擬機規范允許將64位數據的讀寫操作划分為兩次32位的操作來進行,即虛擬機可以選擇不保證64位數據類型的load、store、read、write這4個操作的原子性。

原子性、可見性、有序性

原子性

由Java內存模型提供的8個原子性操作所支持,Long和Double的讀寫大部分商業虛擬機上已實現為原子性操作,更大范圍的原子性操作,Java內存模型還提供了lock和unlock操作來支持,在字節碼層次提供了monitorenter和monitorexit來隱式的使用這兩個操作,反映到java代碼中就是同步代碼塊了 synchronize。

可見性

可見性是指當一個線程修改了共享變量的值,其他線程能夠立即得知這個修改,可見性由volatile支持,除了volatile以外,synchronize和final關鍵字,synchronize的可見性是由”對一個變量執行unlock操作之前,必須先把此變量同步回主內存中“這條規則保證的,而final關鍵字是指當final修飾的字段在構造函數中一旦初始化完成,並且構造器沒有把this的引用傳遞出去,那在其他線程中就能看見final字段的值,無須同步就能被其他線程正確訪問。

有序性

java提供了volatile和synchronize兩個關鍵字來保證線程之間操作的有序性,synchronize是由“一個變量在同一時刻只允許一條線成對其進行lock操作”。

先行發生原則 happens-before

java內存模式下一些天然的先行發生關系,無須任何同步器協助就已經存在,可以再編碼中直接使用,這些規則包括:程序次序規則、管程鎖定規則、volatile變量規則、線程啟動規則、線程終止規則、線程中斷規則、對象終結規則、傳遞性。

參考資料

本文參考:《深入理解Java虛擬機》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM