天天看點

深入了解JVM虛拟機——Java記憶體模型結構之堆的概念

作者:一個即将被退役的碼農

精彩預告: 下一篇我們會詳細介紹“Java記憶體模型結構之方法區”的概念,記得關注哦!

思考一下

學習一項知識總該知道為什麼要學習吧。有人會說,這些寫代碼好像又用不上,貌似所有的事情JVM都替我們做好了。那就,思考一下為什麼要學習JVM虛拟機結構。

那你是否遇到這樣的困惑:堆記憶體該設定多大?OutOfMemoryError異常到底是怎麼引起的?如何進行JVM調優?JVM的垃圾回收是如何?甚至建立一個String對象,JVM都做了些什麼?

這些疑問随着學習的深入都會慢慢得到解答,而要解決這些問題的第一步,就是先了解JVM的構成。

JVM記憶體結構

深入了解JVM虛拟機——Java記憶體模型結構之堆的概念

記憶體結構圖

如果了解了上圖,JVM的記憶體結構基本上掌握了一半。通過上圖我們可以看到什麼?

第一,JVM分為五個區域:虛拟機棧、本地方法棧、方法區、堆、程式計數器。

第二,JVM五個區中虛拟機棧、本地方法棧、程式計數器為線程私有,方法區和堆為線程共享區。圖中已經用顔色區分,綠色表示“通行”,橘黃色表示停一停(需等待)。

第三,JVM不同區域的占用記憶體大小不同,一般情況下堆最大,程式計數器較小。那麼最大的區域會放什麼?當然就是Java中最多的“對象”了。

堆(Heap)

上面已經得出結論,堆記憶體最大,堆是被線程共享,堆的目的就是存放對象。幾乎所有的對象執行個體都在此配置設定。當然,随着優化技術的更新,某些資料也會被放在棧上等。

因為堆占用記憶體空間最大,堆也是Java垃圾回收的主要區域(重點對象),是以也稱作“GC堆”(Garbage Collected Heap)。

核心概念

1.Java堆區在JVM啟動的時候即被建立。是JVM管理的最大一塊記憶體空間,是GC執行垃圾回收的重點區域。

2.一個JVM執行個體隻存在一個堆記憶體,堆也是Java記憶體管理的核心區域,GC和OOM都存在。

《Java虛拟機規範》規定,堆可以處于實體上可以為不連續的記憶體空間中,但在邏輯上它應該被視為連續的。

3.所有的線程共享Java堆,但并不是共享堆的全部,内部同樣存在每個線程私有的緩沖區(如:ThreadLocal Allocation Buffer,TLAB)。

4.《Java虛拟機規範》中對Java堆的描述是:所有的對象執行個體以及數組都應當在運作時配置設定在堆上。因為棧幀中儲存的是引用,這個引用指向對象或者數組在堆中的位置。我要說的是:"幾乎"所有的對象執行個體都在這裡配置設定記憶體,有逃逸現象。

5. 在方法結束後,堆中的對象不會馬上被移除,隻有在垃圾收集的時候才會被移除。

堆空間大小的設定

1. -Xms:設定堆區的起始記憶體,等價于-XX:InitialHeapsize

2. -Xmx:設定堆區的最大記憶體,等價于-XX:MaxHeapSize

3. 一旦堆區中的記憶體大小超過-Xmx所指定的最大記憶體時,将會抛出OutOfMemoryError錯誤。

4. 通常會将-Xms和-Xmx兩個參數配置相同的值,其目的是為了能夠在java垃圾回收機制清理完堆區後不需要重新計算配置設定堆區的大小,進而提高性能。

5. 預設情況下,堆空間初始記憶體大小: 實體電腦記憶體大小 / 64,最大記憶體大小: 實體電腦記憶體大小 / 4

堆記憶體結構細分

在的垃圾收集器大部分都基于分代收集理論設計,JVM堆空間可細分為:

JDK7及以前:堆記憶體邏輯上分為:新生代 + 老年代 + 永久代、TLAB

Young Generation Space 新生代 Young/New:内部又劃分為Eden、Survivor0、Survivor1三個區域

Tenure Generation Space 老年代 Old/Tenure

Permanent Space 永久代 Perm

TLAB

JDK8之後:堆記憶體邏輯上分為:新生代 + 老年代 + 元空間、TLAB

Young Generation Space 新生代 Young/New:内部又劃分為Eden、Survivor0、Survivor1三個區域

Tenure Generation Space 老年代 Old/Tenure

Mete Space 元空間 Meta

TLAB

約定:

新生代 <–> 新生區 <–> 年輕區 --> 新生代

老年代 <–> 老年區 <–> 養老區

永久區 <–> 永久代

新生代與老年代

Java堆區進一步細分的話,可以劃分為年輕代(YoungGen)和老年代(oldGen),預設占比為:1:2

幾乎所有的Java對象都是在Eden區被new出來的。GC對絕大部分的Java對象的回收也是在新生代進行的。

其中年輕代又可以劃分為Eden空間、Survivor0空間和survivor1空間(有時也叫做from區、to區) ,預設占比為:8:1:1(官網上說是8:1:1,但實際檢視是6:1:1,因為存在自适應記憶體配置設定政策)。

深入了解JVM虛拟機——Java記憶體模型結構之堆的概念

設定新生代與老年代在堆結構的占比(這個參數在開發中一般不會調)

-XX:NewRatio=X,預設為2,表示新生代占1,老年代占2,新生代占整個堆的1/3,可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整個堆的1/5

XX:SurvivorRatio=X:設定新生代中Eden區和Survivor區的比例。

-XX:-UseAdaptiveSizePolicy: 關閉自适應記憶體配置設定政策,-XX:+UseAdaptiveSizePolicy:表示打開

-Xmn:新生代最大記憶體大小,這個設定的優先級大于設定比例。

對象配置設定的正常過程

為新對象配置設定記憶體是一件非常嚴謹和複雜的任務,JVM的設計者們不僅需要考慮記憶體如何配置設定、在哪裡配置設定等問題,并且由于記憶體配置設定算法與記憶體回收算法密切相關,是以還需要考慮Gc執行完記憶體回收後是否會在記憶體空間中産生記憶體碎片。

大緻過程如下:

1.new的對象先放Eden區。

2.當Eden的空間填滿時,程式又需要建立對象,JVM才會将對伊甸園區進行垃圾回收(Minor GC),将伊甸園區中的不再被其他對象所引用的對象進行銷毀。

3.然後将伊甸園中的經過Minor GC後幸存的對象移動到幸存者0區(S0),此時Eden區已經清空。

4.如果Eden區再滿了,則再次觸發Minor GC,對Eden區和不為空的Survivor區進行回收,上次幸存下來放到S0區的對象如果還沒有被回收,就會移動到S1區。

5.如果再次經曆垃圾回收,此時會重新放回幸存者0區,接着再去幸存者1區,如此反複,Survivor中總有一個是空的。

6.啥時候能去養老區呢?可以設定次數。預設是15次。可以設定參數:-XX:MaxTenuringThreshold=<N>進行設定。

注意

S0和S1區不能主動的觸發垃圾回收,隻有當Eden區滿了之後被動的進行GC。

下一篇我們會單獨介紹方法區的概念

繼續閱讀