精彩預告: 下一篇我們會詳細介紹“Java記憶體模型結構之方法區”的概念,記得關注哦!
思考一下
學習一項知識總該知道為什麼要學習吧。有人會說,這些寫代碼好像又用不上,貌似所有的事情JVM都替我們做好了。那就,思考一下為什麼要學習JVM虛拟機結構。
那你是否遇到這樣的困惑:堆記憶體該設定多大?OutOfMemoryError異常到底是怎麼引起的?如何進行JVM調優?JVM的垃圾回收是如何?甚至建立一個String對象,JVM都做了些什麼?
這些疑問随着學習的深入都會慢慢得到解答,而要解決這些問題的第一步,就是先了解JVM的構成。
JVM記憶體結構
記憶體結構圖
如果了解了上圖,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,因為存在自适應記憶體配置設定政策)。
設定新生代與老年代在堆結構的占比(這個參數在開發中一般不會調)
-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。
下一篇我們會單獨介紹方法區的概念