JVM知識點整理
- 描述一下JVM記憶體結構
- 每個區的作用是什麼?
- 堆記憶體的工作原理是什麼?為什麼需要兩個Survivor區?隻有一個行不行?
- 老年代使用什麼垃圾回收算法?
JVM記憶體結構
JVM在執行Java程式的過程中會把它管理的記憶體劃分成若幹不同的資料區域。分析JVM記憶體結構就是分析JVM運作時資料存儲區域
JVM運作時的資料區主要包括:堆、虛拟機棧、方法區、程式計數器、本地方法棧
1、堆(GC區)
線程共享
堆是JVM管理記憶體中最大的一塊記憶體區域,堆記憶體被所有線程共享,主要存檔new關鍵字建立的對象,所有對象執行個體以及數組都要在堆上配置設定空間。
垃圾收集器就是根據GC算法,收集堆上所占用的記憶體空間(收集的是對象占用的記憶體空間而不是對象本身)。
堆分為年輕代(Yong Generation)和老年代(Old Generation),年輕代又分為伊甸園區(Eden)和幸存區(Survivor),幸存區又分為From Survivor(S0)和To Survivor(S1)空間。
新建立的對象存儲在年輕代中,當年輕代存滿之後,會觸發Minor GC,清理年輕代記憶體空間。
老年代存儲長期存活的對象和大對象。年輕代中存儲的對象經過多次GC依然存活的對象會移動到老年代存儲。老年代存滿之後會觸發FULL GC。
Minor GC發生在Eden區
Yong GC發生在Eden、S0、S1區
Major GC發生在Old區
FULL GC是清理整個堆空間,包括年輕代和老年代
如果FULL GC之後堆中仍然無法存儲對象,而且堆無法擴充,就發抛出OutOfMemoryError(OOM)錯誤
(1)堆空間比例
預設的,年輕代和老年代的空間比例是1:2,可以通過-XX:NewRatio配置;
預設的,Eden:S0:S1=8:1:1,可以通過-XX:SurvivorRadio配置;
預設的,Survivor中對象被複制的最大次數為15次,可以通過-XX:Max Tenuring Threshold配置;
(2)為什麼年輕代要分成Eden區和Survivor區,為什麼要設定兩個Survivor區
- 如果沒有Survivor區,Eden每進行一次Minor GC,存活的對象就會被送入老年代,老年代很快就被填滿,接着觸發Major GC(因為Major GC一般伴随着Minor GC,也可以看成觸發了FULL GC)。由于老年代空間遠大于年輕代,每進行一次FULL GC消耗的時間比Minor GC長的多,影響程式執行、響應速度,是以要區分Eden區和Survivor區。
- Survivor區存在的意義就是減少被送到老年代的對象,進而減少FULL GC的發生,Survivor的預篩選保證隻有在Survivor經曆15次複制還能在年輕代存活的對象才會被送到老年代。
- 設定兩個Survivor最大的好處就是減少了空間碎片化。如果隻有一塊Survivor區,就會産生下圖這種情況(色塊代表對象占用了的空間)。如果Eden存滿了,Survivor中存了一部分的對象,由于Eden滿了是以會觸發Minor GC,Eden和Survivor中都有一部分的對象存活,接着把Eden中存活的對象轉移到Survivor中,就會導緻空間碎片化。 如果有兩塊Survivor,假設Eden存滿了,S0中有一部分的對象,由于Eden存滿觸發Minor GC,Eden中存活的對象以及S0中存活的對象會被複制到S1中(S1放不下就放到老年代),Eden以及S0被清空。這樣就保證了Survivor中不會空間碎片化。
2、虛拟機棧
線程私有
是描述java方法執行的記憶體模型。
用于存儲局部變量表、操作數棧、動态連結、方法出口等資訊(存儲臨時變量、基本資料類型、對象的引用位址、方法出口等)。
3、方法區
線程共享
存放已被加載的類的資訊、常量、靜态變量、即時編譯器編譯的代碼資料等(jdk7以前稱為永久代、jdk8之後把它改為中繼資料空間)。回收目标主要是常量池的回收和類型的解除安裝。
4、程式計數器
線程私有
目前線程所執行的位元組碼的行号訓示器,用于記錄正在執行的虛拟機位元組指令位址(存儲程式目前運作的位置)。
為了線程切換可以恢複到正确的位置,每個線程都有獨立的程式計數器,不同線程的程式計數器互不影響,獨立存儲。
如果線程執行java方法,程式計數器記錄的是正在執行的虛拟機位元組指令位址;如果執行的Native方法,計數器值為Undefined。
程式計數器這塊記憶體區域是虛拟機規範中唯一沒有OOM的區域。
5、本地方法棧
線程私有
和虛拟機棧相似,隻不過它服務于Native方法。
參考:
JVM記憶體結構
深入了解JVM-記憶體模型(jmm)和GC
一文搞懂JVM記憶體結構
為什麼新生代記憶體需要有兩個Survivor區