JVM G1 GC 算法原理

GC算法原理

在傳統JVM內存管理中,我們把Heap空間分為Young/Old兩個分區,Young分區又包括一個Eden和兩個Survivor分區,如圖1所示。新產生的對象首先會被存放在Eden區,而每次minor GC發生時,JVM一方面將Eden分區內存活的對象拷貝到一個空的Survivor分區,另一方面將另一個正在被使用的Survivor分區中的存活對象也拷貝到空的Survivor分區內。在此過程中,JVM始終保持一個Survivor分區處於全空的狀態。一個對象在兩個Survivor之間的拷貝到一定次數後,如果還是存活的,就將其拷入Old分區。當Old分區沒有足夠空間時,GC會停下所有程序線程,進行Full GC,即對Old區中的對象進行整理。這個所有線程都暫停的階段被稱為Stop-The-World(STW),也是大多數GC算法中對性能影響最大的部分。

JVM G1 GC 算法原理

圖 1 分年代的Heap結構

而G1 GC則完全改變了這一傳統思路。它將整個Heap分為若干個預先設定的小區域塊(如圖2),每個區域塊內部不再進行新舊分區, 而是將整個區域塊標記為Eden/Survivor/Old。當創建新對象時,它首先被存放到某一個可用區塊(Region)中。當該區塊滿了,JVM就會創建新的區塊存放對象。當發生minor GC時,JVM將一個或幾個區塊中存活的對象拷貝到一個新的區塊中,並在空餘的空間中選擇幾個全新區塊作為新的Eden分區。當所有區域中都有存活對象,找不到全空區塊時,才發生Full GC。而在標記存活對象時,G1使用RememberSet的概念,將每個分區外指向分區內的引用記錄在該分區的RememberSet中,避免了對整個Heap的掃描,使得各個分區的GC更加獨立。在這樣的背景下,我們可以看出G1 GC大大提高了觸發Full GC時的Heap佔用率,同時也使得Minor GC的暫停時間更加可控,對於內存較大的環境非常友好。這些顛覆性的改變,將給GC性能帶來怎樣的變化呢?最簡單的方式,我們可以將老的GC設置直接遷移為G1 GC,然後觀察性能變化。

JVM G1 GC 算法原理

圖 2 G1 Heap結構示意

由於G1取消了對於heap空間不同新舊對象固定分區的概念,所以我們需要在GC配置選項上作相應的調整,使得應用能夠合理地運行在G1 GC收集器上。一般來說,對於原運行在Parallel GC上的應用,需要去除的參數包括-Xmn, -XX:-UseAdaptiveSizePolicy, -XX:SurvivorRatio=n等;而對於原來使用CMS GC的應用,我們需要去掉-Xmn -XX:InitialSurvivorRatio -XX:SurvivorRatio -XX:InitialTenuringThreshold -XX:MaxTenuringThreshold等參數。另外在CMS中已經調優過的-XX:ParallelGCThreads -XX:ConcGCThreads參數最好也移除掉,因為對於CMS來說性能最好的不一定是對於G1性能最好的選擇。我們先統一置為默認值,方便後期調優。此外,當應用開啟的線程較多時,最好使用-XX:-ResizePLAB來關閉PLAB()的大小調整,以避免大量的線程通信所導致的性能下降。

關於Hotspot JVM所支持的完整的GC參數列表,可以使用參數-XX:+PrintFlagsFinal打印出來,也可以參見Oracle官方的文檔中對部分參數的解釋。


分享到:


相關文章: