JAVA內存模型

#1、硬件內存和JAVA內存html

  • 硬件的效率與一致性
    輸入圖片說明
  • Java內存模型中規定了全部的變量都存儲在主內存中,每條線程還有本身的工做內存,線程的工做內存中保存了該線程使用到的變量到主內存副本拷貝,線程對變量的全部操做(讀取、賦值)都必須在工做內存中進行,而不能直接讀寫主內存中的變量。不一樣線程之間沒法直接訪問對方工做內存中的變量,線程間變量值的傳遞均須要在主內存來完成
    輸入圖片說明

#2、JAVA內存模型 按照官方的說法:Java 虛擬機具備一個堆,堆是運行時數據區域,全部類實例和數組的內存均今後處分配。
JVM主要管理兩種類型內存:堆和非堆,堆內存(Heap Memory)是在 Java 虛擬機啓動時建立,非堆內存(Non-heap Memory)是在JVM堆以外的內存。 簡單來講,堆是Java代碼可及的內存,留給開發人員使用的;非堆是JVM留給本身用的,包含方法區、JVM內部處理或優化所需的內存(如 JIT Compiler,Just-in-time Compiler,即時編譯後的代碼緩存)、每一個類結構(如運行時常數池、字段和方法數據)以及方法和構造方法的代碼。java

JVM 內存包含以下幾個部分:數組

堆內存(Heap Memory): 存放Java對象
非堆內存(Non-Heap Memory): 存放類加載信息和其它meta-data
其它(Other): 存放JVM 自身代碼等緩存

在JVM啓動時,就已經保留了固定的內存空間給Heap內存,這部份內存並不必定都會被JVM使用,可是能夠肯定的是這部分保留的內存不會被其餘進程使用,這部份內存大小由-Xmx 參數指定。而另外一部份內存在JVM啓動時就分配給JVM,做爲JVM的初始Heap內存使用,這部份內存是由 -Xms 參數指定。安全

#3、JAVA內存分配 Java的內存管理實際上就是變量和對象的管理,其中包括對象的分配和釋放。
輸入圖片說明多線程

JVM內存申請過程以下:併發

  1. JVM 會試圖爲相關Java對象在Eden中初始化一塊內存區域
  2. 當Eden空間足夠時,內存申請結束;不然到下一步
  3. JVM 試圖釋放在Eden中全部不活躍的對象(這屬於1或更高級的垃圾回收),釋放後若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區
  4. Survivor區被用來做爲Eden及OLD的中間交換區域,當OLD區空間足夠時,Survivor區的對象會被移到Old區,不然會被保留在Survivor區
  5. 當OLD區空間不夠時,JVM 會在OLD區進行徹底的垃圾收集(0級)
  6. 徹底垃圾收集後,若Survivor及OLD區仍然沒法存放從Eden複製過來的部分對象,致使JVM沒法在Eden區爲新對象建立內存區域,則出現」out of memory」錯誤

#4、JAVA內存間交互操做 關於主內存與工做內存之間的具體交互協議,即一個變量如何從主內存拷貝到工做內存、如何從工做內存同步到主內存之間的實現細節,Java內存模型定義瞭如下八種操做來完成:app

  • lock(鎖定):做用於主內存的變量,把一個變量標識爲一條線程獨佔狀態。
  • unlock(解鎖):做用於主內存變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其餘線程鎖定。
  • read(讀取):做用於主內存變量,把一個變量值從主內存傳輸到線程的工做內存中,以便隨後的load動做使用
  • load(載入):做用於工做內存的變量,它把read操做從主內存中獲得的變量值放入工做內存的變量副本中。
  • use(使用):做用於工做內存的變量,把工做內存中的一個變量值傳遞給執行引擎,每當虛擬機遇到一個須要使用變量的值的字節碼指令時將會執行這個操做。
  • assign(賦值):做用於工做內存的變量,它把一個從執行引擎接收到的值賦值給工做內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操做。
  • store(存儲):做用於工做內存的變量,把工做內存中的一個變量的值傳送到主內存中,以便隨後的write的操做。
  • write(寫入):做用於主內存的變量,它把store操做從工做內存中一個變量的值傳送到主內存的變量中。

若是要把一個變量從主內存中複製到工做內存,就須要按順尋地執行read和load操做,若是把變量從工做內存中同步回主內存中,就要按順序地執行store和write操做。Java內存模型只要求上述操做必須按順序執行,而沒有保證必須是連續執行。也就是read和load之間,store和write之間是能夠插入其餘指令的,如對主內存中的變量a、b進行訪問時,可能的順序是read a,read b,load b, load a。Java內存模型還規定了在執行上述八種基本操做時,必須知足以下規則:優化

  • 不容許read和load、store和write操做之一單獨出現
  • 不容許一個線程丟棄它的最近assign的操做,即變量在工做內存中改變了以後必須同步到主內存中。
  • 不容許一個線程無緣由地(沒有發生過任何assign操做)把數據從工做內存同步回主內存中。
  • 一個新的變量只能在主內存中誕生,不容許在工做內存中直接使用一個未被初始化(load或assign)的變量。即就是對一個變量實施use和store操做以前,必須先執行過了assign和load操做。
  • 一個變量在同一時刻只容許一條線程對其進行lock操做,lock和unlock必須成對出現
  • 若是對一個變量執行lock操做,將會清空工做內存中此變量的值,在執行引擎使用這個變量前須要從新執行load或assign操做初始化變量的值
  • 若是一個變量事先沒有被lock操做鎖定,則不容許對它執行unlock操做;也不容許去unlock一個被其餘線程鎖定的變量。
  • 對一個變量執行unlock操做以前,必須先把此變量同步到主內存中(執行store和write操做)。

#5、原子性、可見性、有序性ui

  1. 原子性(Atomicity)
    由Java內存模型來直接保證的原子性變量操做包括read、load、use、assign、store和write六個,大體能夠認爲基礎數據類型的訪問和讀寫是具有原子性的。若是應用場景須要一個更大範圍的原子性保證,Java內存模型還提供了lock和unlock操做來知足這種需求,儘管虛擬機未把lock與unlock操做直接開放給用戶使用,可是卻提供了更高層次的字節碼指令monitorenter和monitorexit來隱匿地使用這兩個操做,這兩個字節碼指令反映到Java代碼中就是同步塊---synchronized關鍵字,所以在synchronized塊之間的操做也具有原子性。
  2. 可見性(Visibility)
    可見性就是指當一個線程修改了線程共享變量的值,其它線程可以當即得知這個修改。Java內存模型是經過在變量修改後將新值同步回主內存,在變量讀取前從主內存刷新變量值這種依賴主內存做爲傳遞媒介的方法來實現可見性的,不管是普通變量仍是volatile變量都是如此,普通變量與volatile變量的區別是volatile的特殊規則保證了新值能當即同步到主內存,以及每使用前當即從內存刷新。由於咱們能夠說volatile保證了線程操做時變量的可見性,而普通變量則不能保證這一點。

除了volatile以外,Java還有兩個關鍵字能實現可見性,它們是synchronized。同步塊的可見性是由「對一個變量執行unlock操做以前,必須先把此變量同步回主內存中(執行store和write操做)」這條規則得到的,而final關鍵字的可見性是指:被final修飾的字段是構造器一旦初始化完成,而且構造器沒有把「this」引用傳遞出去,那麼在其它線程中就能看見final字段的值。 3. 有序性(Ordering)
Java內存模型中的程序自然有序性能夠總結爲一句話:若是在本線程內觀察,全部操做都是有序的;若是在一個線程中觀察另外一個線程,全部操做都是無序的。前半句是指「線程內表現爲串行語義」,後半句是指「指令重排序」現象和「工做內存主主內存同步延遲」現象。

Java語言提供了volatile和synchronized兩個關鍵字來保證線程之間操做的有序性,volatile關鍵字自己就包含了禁止指令重排序的語義,而synchronized則是由「一個變量在同一時刻只容許一條線程對其進行lock操做」這條規則來得到的,這個規則決定了持有同一個鎖的兩個同步塊只能串行地進入。

先行發生原則:

若是Java內存模型中全部的有序性都只靠volatile和synchronized來完成,那麼有一些操做將會變得很囉嗦,可是咱們在編寫Java併發代碼的時候並無感受到這一點,這是由於Java語言中有一個「先行發生」(Happen-Before)的原則。這個原則很是重要,它是判斷數據是否存在競爭,線程是否安全的主要依賴。

先行發生原則是指Java內存模型中定義的兩項操做之間的依序關係,若是說操做A先行發生於操做B,其實就是說發生操做B以前,操做A產生的影響能被操做B觀察到,「影響」包含了修改了內存中共享變量的值、發送了消息、調用了方法等。它意味着什麼呢?以下例:

//線程A中執行 i = 1;

//線程B中執行 j = i;

//線程C中執行 i = 2;
假設線程A中的操做」i=1「先行發生於線程B的操做」j=i「,那麼咱們就能夠肯定在線程B的操做執行後,變量j的值必定是等於1,結出這個結論的依據有兩個,一是根據先行發生原則,」i=1「的結果能夠被觀察到;二是線程C登場以前,線程A操做結束以後沒有其它線程會修改變量i的值。如今再來考慮線程C,咱們依然保持線程A和B之間的先行發生關係,而線程C出如今線程A和B操做之間,可是C與B沒有先行發生關係,那麼j的值多是1,也多是2,由於線程C對應變量i的影響可能會被線程B觀察到,也可能觀察不到,這時線程B就存在讀取到過時數據的風險,不具有多線程的安全性。

下面是Java內存模型下一些」自然的「先行發生關係,這些先行發生關係無須任何同步器協助就已經存在,能夠在編碼中直接使用。若是兩個操做之間的關係不在此列,而且沒法從下列規則推導出來的話,它們就沒有順序性保障,虛擬機能夠對它們進行隨意地重排序。

a.程序次序規則(Pragram Order Rule):在一個線程內,按照程序代碼順序,書寫在前面的操做先行發生於書寫在後面的操做。準確地說應該是控制流順序而不是程序代碼順序,由於要考慮分支、循環結構。

b.管程鎖定規則(Monitor Lock Rule):一個unlock操做先行發生於後面對同一個鎖的lock操做。這裏必須強調的是同一個鎖,而」後面「是指時間上的前後順序。

c.volatile變量規則(Volatile Variable Rule):對一個volatile變量的寫操做先行發生於後面對這個變量的讀取操做,這裏的」後面「一樣指時間上的前後順序。

d.線程啓動規則(Thread Start Rule):Thread對象的start()方法先行發生於此線程的每個動做。

e.線程終於規則(Thread Termination Rule):線程中的全部操做都先行發生於對此線程的終止檢測,咱們能夠經過Thread.join()方法結束,Thread.isAlive()的返回值等做段檢測到線程已經終止執行。

f.線程中斷規則(Thread Interruption Rule):對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生,能夠經過Thread.interrupted()方法檢測是否有中斷髮生。

g.對象終結規則(Finalizer Rule):一個對象初始化完成(構造方法執行完成)先行發生於它的finalize()方法的開始。

h.傳遞性(Transitivity):若是操做A先行發生於操做B,操做B先行發生於操做C,那就能夠得出操做A先行發生於操做C的結論。

一個操做」時間上的先發生「不表明這個操做會是」先行發生「,那若是一個操做」先行發生「是否就能推導出這個操做一定是」時間上的先發生「呢?也是不成立的,一個典型的例子就是指令重排序。因此時間上的前後順序與先生髮生原則之間基本沒有什麼關係,因此衡量併發安全問題一切必須以先行發生原則爲準。

#6、JAVA內存管理 參考文獻:http://www.blogjava.net/chhbjh/archive/2012/01/28/368936.html

#7、JAVA安全沙箱 參考文獻:https://my.oschina.net/xionghui/blog/499725

相關文章
相關標籤/搜索