上一遍文章咱們講到了CPU緩存一致性以及內存屏障問題。那麼Java做爲一個跨平臺的語言,它的實現要面對不一樣的底層硬件系統,設計一箇中間層模型來屏蔽底層的硬件差別,給上層的開發者一個一致的使用接口。Java內存模型就是這樣一箇中間層的模型,它爲程序員屏蔽了底層的硬件實現細節,支持大部分的主流硬件平臺。html
java內存模型(Java Memory Mode):java內存模型是java虛擬機內存如何與計算機內存(RAM)一塊兒工做。java虛擬機是是整個計算機的模型,因此這個模型天然包含一個內存模型。也能夠說JMM是java虛擬機內存使用規範。java
通俗的來說,就是描述Java中各類變量(線程共享變量)的訪問規則,以及在JVM中將變量存儲到內存和從內存中讀取變量這樣的底層細節。程序員
Java內存模型規定了不一樣線程如何以及什麼時候能夠看到其餘線程寫入共享變量的值以及如何在必要時同步對共享變量的訪問。數組
注意:Java Memory Model並非真實存在的,他只是物理內存模型的一個映射。緩存
JVM中內存分配的兩個概念:架構
stack(棧)
特色: 存取速度快、對象生命週期肯定、數據大小肯定。
存儲數據:基本類型變量、對象引用(句柄)
位置:緩存、寄存器、寫緩衝區。併發
heap(堆)
特色: 存取速度慢、運行時動態分配大小、對象生命抽週期不肯定、垃圾回收。
存儲數據:對象
位置:主內存、緩存線程
理論上說全部的stack和heap都存儲在物理主內存中,但隨着CPU運算其數據的副本可能被緩存或者寄存器持有,持有的數據聽從一致性協議.設計
上面說到,一個對象時存儲在heap上的,對象中所屬的方法與方法的成員變量存儲在stack上。一個對象的成員變量隨着對象自己存儲在堆上,不管該對象類型是引用類型或者是基本類型。 靜態變量和對象類定義存儲於堆上。htm
存儲在堆上的對象能夠被持有該對象引用的棧訪問。能訪問對象,也就能訪問該對象中的成員變量。當了兩個線程同時訪問一個對象時,每一個線程都擁有該對象成員變量的私有拷貝。
這裏只是粗略分配了java內存模型。具體細節的內存分配請查看
JVM內存管理概述
咱們來看看一個關係圖:
在系統內存架構中並無棧(stack)、堆(heap)這種概念,只有寄存器(register)、緩存(cache)、主內存(RAM、Main Memory)。理論上說全部的棧和堆都存儲在主內存中,但隨着CPU運算其數據的副本可能被緩存或者寄存器持有。持有的數據聽從CPU-Cache一致性協議。
CPU內存模型、一致性協議能夠參考前一篇文章死磕併發之CPU緩存一致性協議(MESI)
主內存:保存了全部的變量。
共享變量:若是一個變量被多個線程使用,那麼這個變量會在每一個線程的工做內存中保有一個副本,這種變量就是共享變量。
好比成員變量、靜態變量、數組元素等。
工做內存:每一個線程都有本身的工做內存,線程獨享,保存了線程用到了變量的副本(主內存共享變量的一份拷貝)。工做內存負責與線程交互,也負責與主內存交互。爲了更高的效率java虛擬機、硬件系統可能讓工做內優先分配在寄存器、緩存中。
JMM對共享內存的操做作出了以下兩條規定:
假設線程A和線程B同事訪問某個對象的成員變量x。當線程a須要操做變量a,時會將a副本複製到線程A的工做內存中。
當線程a未執行完畢,線程b也要訪問變量a
可是線程a與線程b操做的是本身工做空間中的變量副本。 線程a中的副本和線程b中間的副本相符不可見。若是a線程率先完成了任務並寫回主存。那麼線程b的運算就是在使用後髒數據運算。若是b也寫回主存那麼線程a的任務就會丟失。
爲了保證程序的準確性,咱們就須要在併發時添加額外的同步操做。
咱們接着再來關注下變量從主內存讀取到工做內存,而後同步回工做內存的細節,這就是主內存與工做內存之間的交互協議。Java內存模型定義瞭如下8種操做來完成,它們都是原子操做(除了對long和double類型的變量)。
鎖定(lock):做用於主內存中的變量,將他標記爲一個線程獨享變量。
一般意義上的上鎖,就是一個線程正在使用時,其餘線程必須等待該線程任務完成才能繼續執行本身的任務。
解鎖(unlock):做用於主內存中的變量,解除變量的鎖定狀態,被解除鎖定狀態的變量才能被其餘線程鎖定。
執行完成後解開鎖。
read(讀取):做用於主內存的變量,它把一個變量的值從主內存傳輸到線程的工做內存中,以便隨後的load動做使用。
從主內存 讀取到工做內存中。
load(載入):把read操做從主內存中獲得的變量值放入工做內存的變量的副本中。
給工做內存中的副本賦值。
use(使用):把工做內存中的一個變量的值傳給執行引擎,每當虛擬機遇到一個使用到變量的指令時都會使用該指令。
程序執行過程當中讀取該值時調用。
assign(賦值):做用於工做內存的變量,它把一個從執行引擎接收到的值賦給工做內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操做。
將運算完成後的新值賦回給工做內存中的變量,至關於修改工做內存中的變量。
store(存儲):做用於工做內存的變量,它把工做內存中一個變量的值傳送到主內存中,以便隨後的write操做使用。
將該值從變量中取出,寫入工做內存中。
write(寫入):做用於主內存的變量,它把store操做從工做內存中獲得的變量的值放入主內存的變量中。
將工做內存中的值寫回主內存。
不容許read和load、store和write操做之一單獨出現,即不容許一個變量從主內存讀取了但工做內存不接受,或者從工做內存發起回寫了但主內存不接受的狀況出現。
不容許一個線程丟棄它的最近的assign操做,即變量在工做內存中改變了以後必須把該變化同步回主內存。
不容許一個線程無緣由地(沒有發生過任何assign操做)把數據從線程的工做內存同步回主內存中。
一個新的變量只能在主內存中「誕生」,不容許在工做內存中直接使用一個未被初始化(load或assign)的變量,換句話說就是對一個變量實施use和store操做以前,必須先執行過了assign和load操做。
一個變量在同一個時刻只容許一條線程對其進行lock操做,但lock操做能夠被同一條線程重複執行屢次,屢次執行lock後,只有執行相同次數的unlock操做,變量纔會被解鎖。
若是對一個變量執行lock操做,將會清空工做內存中此變量的值,在執行引擎使用這個變量前,須要從新執行load或assign操做初始化變量的值。
若是一個變量事先沒有被lock操做鎖定,則不容許對它執行unlock操做,也不容許去unlock一個被其餘線程鎖定住的變量。