來自微信公眾號:無敵碼農
JVM結構示意圖
JVM總體概述
JVM總體上是由類裝載子系統(ClassLoader)、執行時資料區、執行引擎、記憶體回收這四個部分組成。其中我們最為關註的執行時資料區,也就是JVM的記憶體部分則是由方法區(Method Area)、JAVA堆(Heap)、虛擬機器棧(Stack)、程式計數器、本地方法棧這幾部分組成;除此以外,在概念中還有一個直接記憶體的概念,事實上這部分記憶體並不屬於虛擬機器規範中定義的記憶體區域,但是因為在JDK1.4+後新加的NIO類,以及JDK1.8+後的Metaspace的關係,所以在討論JVM時也經常會被放到一起討論。
JVM記憶體概述
各記憶體部分的功能及具體組成部分,總結如下:
需要說明的是,堆記憶體是GC重點回收區域,其中分代回收機制將堆記憶體劃分為年輕代、老年代兩個區域,預設情況下年輕代佔整個堆記憶體空間的1/3,而老年代則佔2/3,可以透過“-XX:NewRatio”設定年輕代與老年代的比值,預設為2,表示比值年輕代與老年代的比值為“1:2”,在JVM調優時可根據應用實際情況進行調整。
而年輕代又分為Eden、Survivor0、Survivor1,這三個區域佔整個新生代空間的比值為8:1:1,即Eden區佔8/10,其他兩個區域分別佔1/10,可透過“-XX:SurvivorRatio”引數進行設定,預設值為8。
正確理解併發問題
在瞭解了JVM結構,特別是記憶體結構後,我們再說說併發問題產生的原因。在上面的內容中我們分析了Java堆、Java棧,知道Java堆儲存的是物件,而Java棧記憶體是方法執行時所需要的區域性變數,其中就包括堆中物件的取用,如果多個執行緒同時修改堆中同一取用物件的資料,就可能會產生併發問題,導致多個執行緒中某些執行緒得到的資料值與實際值不符,造成臟資料。
那麼這種問題為什麼會發生呢?
實際上,執行緒操作堆中共享物件資料時並不是直接操作物件所在的那塊記憶體,這裡稱之為主記憶體;而是將物件複製到執行緒私有的工作記憶體中進行更新,完成後再將最新的資料值同步回主記憶體,而多個執行緒在同一時刻將一個物件的值改得七七八八,然後再同時同步給物件所在的記憶體區域,那麼以誰更新的為準就成了問題了。
所以,為了防止這種情況出現,Java提供了同步機制,確保各個執行緒按照一定的機制同一時刻只能執行一個執行緒更新主記憶體的值。
具體邏輯示意圖如下:
註意,這裡所講的主記憶體、工作記憶體與Java記憶體區域中的Java堆、棧記憶體、方法區等並不是同一個層次的記憶體劃分。如果勉強類比,從變數、主記憶體、工作記憶體的定義來看,主記憶體主要對應於Java堆中物件實體資料部分,而工作記憶體則對應於虛擬機器棧中使用的部分記憶體區域;從更低層次類比,主記憶體就直接對應於物理硬體的記憶體,而為了獲取更好的執行速度,虛擬機器(甚至是硬體系統本身的最佳化措施)可能會讓記憶體優先儲存於暫存器和高速快取中,因為程式執行時主要訪問讀寫的是工作記憶體。
而主記憶體與工作記憶體之間具體的互動協議,即一個變數如何從主記憶體複製到工作記憶體、如何從工作記憶體同步回主記憶體之間的實現細節,Java記憶體模型中定義了8種操作來完成。
而且還規定在執行上述8種基本操作時必須滿足如下規則:
-
不允許read和load、store和write操作之一單獨出現,即不允許一個變數從主記憶體讀取了但工作記憶體不接受,或者從工作記憶體發起了回寫了但主記憶體不接受的情況出現。
-
不允許一個執行緒丟棄它的最近的assign操作,即變數在工作記憶體中改變了之後必須把該變化同步回主記憶體。
-
不允許一個執行緒無原因地(沒有發生任何assign操作)把資料從執行緒的工作記憶體同步回主記憶體中。
-
一個新的變數只能在主記憶體中“誕生”,不允許在工作記憶體中直接使用一個未被初始化(load或assign)的變數,換句話說,就是對一個變數實施use、store操作之前,必須先執行過了assign和load操作。
-
一個變數在同一時刻只允許一條執行緒對其進行lock操作,但lock操作可以被同一條執行緒重覆執行多次,多次執行lock後,只有執行相同次數的unlock操作,變數才會被解鎖。
-
如果對一個變數執行lock操作,那將會清空工作記憶體中此變數的值,在執行引擎使用這個變數前,需要重新執行load或assign操作初始化變數的值。
-
如果一個變數事先沒有被lock操作鎖定,那就不允許對它執行unlock操作,也不允許去unlock一個被其他執行緒鎖定住的變數。
-
對一個變數執行unlock操作之前,必須先把此變數同步回主記憶體中(執行store、write操作)。
以上8種記憶體訪問操作以及上述規則限定,再加上volatile的一些特殊規定以及final不可變特性,就已經完成確定了JAVA程式中那些記憶體訪問操作在併發下是安全的!
JVM引數總結
為了方便大家對於JVM有關引數有一個參照,如下:
後記
讀到這裡,小編希望能夠對大家溫習基礎知識起到一定的幫助,特別是從事Java開發工作時間並不長的朋友希望本文能對你們有所促進,因為根據作者的經驗,有時候很多從事Java開發工作好幾年的同學,都會對這些知識點產生模糊的認識,一方面是目前各類Java開源工具比較完備,另一方面是很多人從事的是業務研發工作,時間久了難免會對基礎知識有所遺忘。在上面的部分中還有一塊垃圾回收的知識點沒有總結到,基於篇幅的原因後面再單獨給大家總結!謝謝你們的關註~
—————END—————
●編號792,輸入編號直達本文
●輸入m獲取文章目錄
演演算法與資料結構
更多推薦《18個技術類公眾微信》
涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。