1. 程式人生 > 實用技巧 >一個試圖瞭解JVM記憶體模型的兩年經驗的初級程式設計師,透徹!

一個試圖瞭解JVM記憶體模型的兩年經驗的初級程式設計師,透徹!

所有的程式語言中都有記憶體模型這個概念,區別於微架構的記憶體模型,高階語言的記憶體模型包括了編譯器和微架構兩部分。我試圖瞭解了Java、C#和Go語言的記憶體模型,發現內容基本大同小異,只是這些語言在具體實現的時候略有不同。

我們來看看Java記憶體模型吧,提到Java記憶體模型大家對這個圖一定非常熟悉:

這張圖告訴我們線上程執行的時候有一個記憶體專用的一小塊記憶體,當Java程式會將變數同步到執行緒所在的記憶體,這時候會操作工作記憶體中的變數,而執行緒中變數的值何時同步回主記憶體是不可預期的。但同時Java記憶體模型又告訴我們通過使用關鍵詞“synchronized”或“volatile”可以讓Java保證某些約束:

“volatile” — 保證讀寫的都是主記憶體的變數
“synchronized” —保證在塊開始時都同步主記憶體的值到工作記憶體,而塊結束時將變數同步回主記憶體

通過以上描述我們就可以寫出執行緒安全的Java程式,JDK也同時幫我們遮蔽了很多底層的東西。

但當你深入瞭解JVM的時候你會發現根本就沒有工作記憶體這個東西,即記憶體中根本不會分配這麼一塊空間來執行你的Java程式,那麼工作記憶體到底是什麼東西呢?

這個問題也曾經困擾了我很長時間,因為我從來沒有從JVM的實現中找到過和主記憶體同步的程式碼,因為當使用“volatile”時我僅僅能從原始碼中呼叫了這行語句:

__asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");

而這個指令在部分微架構上的主要功能就是防止指令重排,即這條指令前後的其它指令不會越過這個界限執行[注1]。

在現在的x86/x64微架構中讀寫記憶體的一致性都是通過MESI(Intel使用MESI-F,AMD使用MOESI)協議保證[注2],MESI的狀態轉換圖如下:

那Java記憶體模型中所說的工作記憶體是什麼呢?
我的理解是,首先“工作記憶體”是一個虛擬的概念,而承載這個概念主要是兩部分:

1. 編譯器
2. 微架構

作為編譯器肯定是執行速度越快越好,所以作為編譯器應當儘量減少從記憶體讀資料,如果一個數據在暫存器中,那麼直接使用暫存器中的值無疑效能是最高的,但同時這也會導致可能讀不到最新的值,這裡我們通過在Java語言中為變數加上“volatile”強制告訴編譯器這個變數一定要從記憶體獲得,這時編譯器即不會做此類優化【案例見參考資料5(是一個.Net的例子)】。

對於微架構來說,在x86/x64下,CPU會在執行指令時做指令重排,即編譯器生成的指令順序和真正在CPU執行的順序可能是不一致的。當我們用一個變數做訊號的時候這種指令重排會帶來悲劇,即如果有如下程式碼:

x = 0;
y = 0;
i = 0;
j = 0;
// thread A
y = 1;
x = 1;
// thread B
i = x;
j = y;

上面的程式碼i和j的值會是多少呢?答案是:“00, 01, 10, 11”都是有可能的。
對於這種情況,如果我們想得到確定的結果則需要通過“synchronized”(或者j.c.u.locks)來做執行緒間同步。

所以,我個人對Java記憶體模型的理解是:在編譯器各種優化及多種型別的微架構平臺上,Java語言規範制定者試圖建立一個虛擬的概念並傳遞到Java程式設計師,讓他們能夠在這個虛擬的概念上寫出執行緒安全的程式來,而編譯器實現者會根據Java語言規範中的各種約束在不同的平臺上達到Java程式設計師所需要的執行緒安全這個目的。

最後

私信回覆 資料 領取一線大廠Java面試題總結+阿里巴巴泰山手冊+各知識點學習思維導+一份300頁pdf文件的Java核心知識點總結!

這些資料的內容都是面試時面試官必問的知識點,篇章包括了很多知識點,其中包括了有基礎知識、Java集合、JVM、多執行緒併發、spring原理、微服務、Netty 與RPC 、Kafka、日記、設計模式、Java演算法、資料庫、Zookeeper、分散式快取、資料結構等等。