JVM之內存模型JMM中本地內存的理解

1、JMM產生背景和定義

JMM(Java內存模型)源於物理機器CPU架構的內存模型,最初用於解決MP(多處理器架構)系統中的緩存一致性問題,而JVM爲了屏蔽各個硬件平臺和操作系統對內存訪問機制的差異化,提出了JMM的概念。Java內存模型是一種虛擬機規範,JMM規範了Java虛擬機與計算機內存是如何協同工作的:規定了一個線程如何和何時可以看到由其他線程修改過後的共享變量的值,以及在必須時如何同步的訪問共享變量。

2、Java內存模型和操作系統內存模型的關係

Java內存模型和操作系統內存模型的關係
參考文章:Java Memory Model(http://tutorials.jenkov.com/java-concurrency/java-memory-model.html) 這個英文博客講的JMM很詳細,很有參考意義

Java內存模型的主要目標是定義程序中各個變量的訪問規則。此處提到的變量只包含了實例對象、靜態對象和構成數組對象的元素。局部變量和方法參數是線程私有的,不會共享,當然不存在數據競爭問題(如果局部變量是一個reference引用類型,它引用的對象在Java堆中可被各個線程共享,但是reference引用本身在Java棧的局部變量表中,是線程私有的)。爲了獲得較高的執行效能,Java內存模型並沒有限制執行引起使用處理器的特定寄存器或者緩存來和主內存進行交互,也沒有限制即時編譯器進行調整代碼執行順序這類優化措施。

JMM規定了所有的變量都存儲在主內存(Main Memory)中。每個線程還有自己的工作內存(Working Memory),線程的工作內存中保存了該線程使用到的變量的主內存的副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在工作內存中進行,而不能直接讀寫主內存中的變量(volatile變量仍然有工作內存的拷貝,但是由於它特殊的操作順序性規定,所以看起來如同直接在主內存中讀寫訪問一般)。不同的線程之間也無法直接訪問對方工作內存中的變量,線程之間值的傳遞都需要通過主內存來完成。

對於JMM與JVM本身的內存模型,參照《深入理解Java虛擬機》的解釋,主內存、工作內存與Java內存區域中的Java堆、棧、方法區等並不是同一個層次的內存劃分。如果兩者一定要勉強對應起來,那從變量、主內存、工作內存的定義來看,主內存主要對應於Java堆中對象的實例數據部分,而工作內存則對應於虛擬機棧中的部分區域。從更低的層次來說,主內存就是硬件的內存,而爲了獲取更好的運行速度,虛擬機及硬件系統可能會讓工作內存優先存儲於寄存器和告訴緩衝中。

3、Java內存模型的抽象結構

在Java中,所有實例域、靜態域和數組元素都存儲在堆內存中,堆內存在線程之間共享(本章用「共享變量」這個術語代指實例域,靜態域和數組元素)。局部變量(Local Variables),方法定義參數(Java語言規範稱之爲Formal Method Parameters)和異常處理器參數(Exception Handler Parameters)不會在線程之間共享,它們不會有內存可見性問題,也不受內存模型的影響。

Java線程之間的通信由Java內存模型(本文簡稱爲JMM)控制,JMM決定一個線程對共享變量的寫入何時對另一個線程可見。從抽象的角度來看,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存(Main Memory)中,每個線程都有一個私有的本地內存(Local Memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存、寫緩衝區、寄存器以及其他的硬件和編譯器優化。Java內存模型的抽象示意如圖所示:
Java內存模型

從圖來看,如果線程A與線程B之間要通信的話,必須要經歷下面2個步驟。
1)線程A把本地內存A中更新過的共享變量刷新到主內存中去。
2)線程B到主內存中去讀取線程A之前已更新過的共享變量。
下面通過示意圖來說明這兩個步驟。
這裏寫圖片描述

如上圖所示,本地內存A和本地內存B由主內存中共享變量x的副本。假設初始時,這3個內存中的x值都爲0。線程A在執行時,把更新後的x值(假設值爲1)臨時存放在自己的本地內存A中。當線程A和線程B需要通信時,線程A首先會把自己本地內存中修改後的x值刷新到主內存中,此時主內存中的x值變爲了1。隨後,線程B到主內存中去讀取線程A更新後的x值,此時線程B的本地內存的x值也變爲了1。從整體來看,這兩個步驟實質上是線程A在向線程B發送消息,而且這個通信過程必須要經過主內存。JMM通過控制主內存與每個線程的本地內存之間的交互,來爲Java程序員提供內存可見性保證。

參考《Java併發編程的藝術》