一張圖看懂JVM

一張圖看懂JVM(本文以1.7版本爲例,1.8永久代更改爲元空間自行總結)

JVM結構示意圖


注意: Eden:S0:S1=8:1:1,上圖標識有誤!!

JVM總體上是由類裝載子系統(ClassLoader)、運行時數據區、執行引擎、內存回收這四個部分組成。其中我們最爲關注的運行時數據區,也就是JVM的內存部分則是由方法區(Method Area)、JAVA堆(Heap)、虛擬機棧(Stack)、程序計數器、本地方法棧這幾部分組成;除此以外,在概念中還有一個直接內存的概念,事實上這部分內存並不屬於虛擬機規範中定義的內存區域,但是因爲在JDK1.4+後新加的NIO類,以及JDK1.8+後的Metaspace的關係,所以在討論JVM時也經常會被放到一起討論。

各內存部分的功能及具體組成部分,總結如下:

![在這裏插入圖片描述](https://img-blog.csdn.net/20180919170723910?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMxNDU3NjY1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

需要說明的是,堆內存是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開源工具比較完備,另一方面是很多人從事的是業務研發工作,時間久了難免會對基礎知識有所遺忘。在上面的部分中還有一塊垃圾回收的知識點沒有總結到,基於篇幅的原因後面再單獨給大家總結!謝謝你們的關注~

本文轉自:微信公衆號-無敵碼農

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章