JVM 09.3 執行時資料區 堆 調優/垃圾回收/小結
阿新 • • 發佈:2020-07-17
版權宣告:源出處:尚矽谷JVM
部落格來源於大佬整理
常用調優工具
1.JDK命令列
2.Eclipse:Memory Analyzer Tool
3.Jconsole
4.VisualVM
5.Jprofiler
6.Java Flight Recorder
7.GCViewer
8.GC Easy
Minor GC、Major GC、Full GC
JVM在進行GC時,並非每次都針對上面三個記憶體區域(新生代、老年代、方法區)一起回收的,大部分時候回收都是指新生代。
針對hotSpot VM的實現,它裡面的GC按照回收區域又分為兩大種類型:一種是部分收集(Partial GC),一種是整堆收集(Full GC)
1.部分收集:不是完整收集整個Java堆的垃圾收集。其中又分為:
- 新生代收集(Minor GC/Young GC):只是新生代的垃圾收集
- 老年代收集(Major GC/Old GC):只是老年代的垃圾收集
- 目前,只有CMS GC會有單獨收集老年代的行為
- 注意,很多時候Major GC 會和 Full GC混淆使用,需要具體分辨是老年代回收還是整堆回收
- 混合收集(Mixed GC):收集整個新生代以及部分老年代的垃圾收集。
- 混合收集不涉及方法區回收,只是新生代,老年代的混合收集。
- 目前,之後G1 GC會有這種行為。
2.整堆收集(Full GC):收集整個java堆和方法區的垃圾收集。
不同GC的觸發機制
年輕代GC(Minor GC)觸發機制:- 當年輕代空間不足時,就會觸發Minor GC,這裡的年輕代滿指的是Eden代滿,Survivor滿不會引發GC.(每次Minor GC會清理年輕代的記憶體,Survivor是被動GC,不會主動GC)
- 因為Java隊形大多都具備朝生夕滅的特性,所以Monor GC 非常頻繁,一般回收速度也比較快,這一定義既清晰又利於理解。
- Minor GC 會引發STW(Stop the World),暫停其他使用者的執行緒,等垃圾回收結束,使用者執行緒才恢復執行。
- 指發生在老年代的GC,物件從老年代消失時,Major GC 或者 Full GC 發生了。
- 出現了Major GC,經常會伴隨至少一次的Minor GC(不是絕對的,在Parallel Scavenge 收集器的收集策略裡就有直接進行Major GC的策略選擇過程)
- 也就是老年代空間不足時,會先嚐試觸發Minor GC。如果之後空間還不足,則觸發Major GC
- Major GC速度一般會比Minor GC慢10倍以上,STW時間更長
- 如果Major GC後,記憶體還不足,就報OOM了
- 觸發Full GC執行的情況有以下五種
- ①呼叫System.gc()時,系統建議執行Full GC,但是不必然執行
- ②老年代空間不足
- ③方法區空間不足
- ④通過Minor GC後進入老年代的平均大小小於老年代的可用記憶體
- ⑤由Eden區,Survivor S0(from)區向S1(to)區複製時,物件大小由於To Space可用記憶體,則把該物件轉存到老年代,且老年代的可用記憶體小於該物件大小
- 說明:Full GC 是開發或調優中儘量要避免的,這樣暫停時間會短一些
堆空間分代思想
為什麼要把Java堆分代?不分代就不能正常工作了麼
- 經研究,不同物件的生命週期不同。70%-99%的物件都是臨時物件。
- 新生代:有Eden、Survivor構成(s0,s1 又稱為from to),to總為空
- 老年代:存放新生代中經歷多次依然存活的物件
- 其實不分代完全可以,分代的唯一理由就是優化GC效能。如果沒有分代,那所有的物件都在一塊,就如同把一個學校的人都關在一個教室。GC的時候要找到哪些物件沒用,這樣就會對堆的所有區域進行掃描,而很多物件都是朝生夕死的,如果分代的話,把新建立的物件放到某一地方,當GC的時候先把這塊儲存“朝生夕死”物件的區域進行回收,這樣就會騰出很大的空間出來。
記憶體分配策略總結
- 如果物件在Eden出生並經過第一次Minor GC後依然存活,並且能被Survivor容納的話,將被移動到Survivor空間中,把那個將物件年齡設為1.物件在Survivor區中每熬過一次MinorGC,年齡就增加一歲,當它的年齡增加到一定程度(預設15歲,其實每個JVM、每個GC都有所不同)時,就會被晉升到老年代中
- 物件晉升老年代的年齡閾值,可以通過選項 -XX:MaxTenuringThreshold來設定
- 針對不同年齡段的物件分配原則如下:
- 優先分配到Eden
- 大物件直接分配到老年代
- 我們要儘量避免程式中出現過多的大物件
- 長期存活的物件分配到老年代
- 動態物件年齡判斷
- 如果Survivor區中相同年齡的所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的物件可以直接進入到老年代。無需等到MaxTenuringThreshold中要求的年齡
大物件直接進入老年代舉例
分配60m堆空間,新生代 20m ,Eden 16m, s0 2m, s1 2m,buffer物件20m,Eden 區無法存放buffer, 直接晉升老年代。
/** 測試:大物件直接進入老年代
* -Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails
*/
public class YoungOldAreaTest {
// 新生代 20m ,Eden 16m, s0 2m, s1 2m
// 老年代 40m
public static void main(String[] args) {
//Eden 區無法存放buffer 晉升老年代
byte[] buffer = new byte[1024 * 1024 * 20];//20m
}
}
日誌輸出:
空間分配擔保機制
簡單解釋一下為什麼會出現這種情況:因為給 allocation2 分配記憶體的時候 eden 區記憶體幾乎已經被分配完了,我們剛剛講了當 Eden 區沒有足夠空間進行分配時,虛擬機器將發起一次 Minor GC.GC 期間虛擬機器又發現 allocation1 無法存入 Survivor 空間,所以只好通過分配擔保機制:把新生代的物件提前轉移到老年代中去,老年代上的空間足夠存放 allocation1,所以不會出現 Full GC。執行 Minor GC 後,後面分配的物件如果能夠存在 eden 區的話,還是會在 eden 區分配記憶體。可以執行如下程式碼驗證:
public class GCTest {
public static void main(String[] args) {
byte[] allocation1, allocation2,allocation3,allocation4,allocation5;
allocation1 = new byte[32000*1024];
allocation2 = new byte[1000*1024];
allocation3 = new byte[1000*1024];
allocation4 = new byte[1000*1024];
allocation5 = new byte[1000*1024];
}
}