垃圾回收之G1收集過程

2023-04-03 21:01:06

G1 中提供了 Young GC、Mixed GC 兩種垃圾回收模式,這兩種垃圾回收模式,都是 Stop The World(STW) 的。

G1 沒有 fullGC 概念,需要 fullGC 時,呼叫 serialOldGC 進行全堆掃描(包括 eden、survivor、o、perm)。

 

一、G1 堆記憶體結構

堆記憶體會被切分成為很多個固定大小區域(Region),每個是連續範圍的虛擬記憶體。

1、Region

堆記憶體中一個區域 (Region) 的大小,可以通過 -XX:G1HeapRegionSize 引數指定,大小區間最小 1M 、最大 32M ,總之是 2 的冪次方。

預設是將堆記憶體按照 2048 份均分。

 

 

每個 Region 被標記了 E、S、O 和 H,這些區域在邏輯上被對映為 Eden,Survivor 和老年代。

存活的物件從一個區域轉移(即複製或移動)到另一個區域。區域被設計為並行收集垃圾,可能會暫停所有應用執行緒。如上圖所示,區域可以分配到 Eden,survivor 和老年代。

此外,還有第四種型別,被稱為巨型區域(Humongous Region)。

Humongous 區域主要是為儲存超過 50% 標準 region 大小的物件設計,它用來專門存放巨型物件。如果一個 H 區裝不下一個巨型物件,那麼 G1 會尋找連續的 H 分割區來儲存。為了能找到連續的 H 區,有時候不得不啟動 Full GC 。

 

2、小物件

G1預設啟用了UseTLAB優化,建立物件(小物件)時,優先從TLAB中分配記憶體,如果分配失敗,說明當前TLAB的剩餘空間不滿足分配需求,則呼叫allocate_new_tlab方法重新申請一塊TLAB空間,之前都是從eden區分配,G1需要從eden region中分配,不過也有可能TLAB的剩餘空間還比較大,JVM不想就這麼浪費掉這些記憶體,就會從eden region中分配記憶體。

 

3、大物件

要特別注意的是,巨型物件(Humongous Object),即大小超過 3/4 的 Region 大小的物件會作特殊處理,分配到由一個或多個連續 Region 構成的區域。巨型物件會引起其他一些問題。

 

二、停頓預測模型

Pause Prediction Model 即停頓預測模型。

它在G1中的作用是: >G1 uses a pause prediction model to meet a user-defined pause time target and selects the number of regions to collect based on the specified pause time target.

G1 GC是一個響應時間優先的GC演演算法,它與CMS最大的不同是,使用者可以設定整個GC過程的期望停頓時間,引數-XX:MaxGCPauseMillis指定一個G1收集過程目標停頓時間,預設值200ms,不過它不是硬性條件,只是期望值。

G1根據這個模型統計計算出來的歷史資料來預測本次收集需要選擇的Region數量,從而儘量滿足使用者設定的目標停頓時間。

停頓預測模型是以衰減標準偏差為理論基礎實現的:

//  share/vm/gc_implementation/g1/g1CollectorPolicy.hpp
double get_new_prediction(TruncatedSeq* seq) {
    return MAX2(seq->davg() + sigma() * seq->dsd(),
                seq->davg() * confidence_factor(seq->num()));
}

在這個預測計算公式中:davg表示衰減均值,sigma()返回一個係數,表示信賴度,dsd表示衰減標準偏差,confidence_factor表示可信度相關係數。

而方法的引數TruncateSeq,顧名思義,是一個截斷的序列,它只跟蹤了序列中的最新的n個元素。

 

三、YoungGC 年輕代收集

在分配一般物件(非巨型物件)時,當所有 eden region 使用達到最大閥值、並且無法申請足夠記憶體時,會觸發一次 YoungGC 。

每次 younggc 會回收所有Eden 、以及 Survivor 區,並且將存活物件複製到 Old 區以及另一部分的 Survivor 區。

 

第一階段:掃描根

跟 CMS 類似,Stop the world,掃描 GC Roots 物件;

 

第二階段:處理 Dirty card,更新 RSet

處理 dirty card queue 中的 card,更新 RSet。此階段完成後,RSet 可以準確的反映老年代對所在的記憶體分段中物件的參照。

 

第三階段:掃描 RSet

掃描 RSet 中所有 old 區,對掃描到的 young 區或者 survivor 區的參照;

第四階段:複製掃描出的存活的物件到 survivor2/old 區

Eden 區記憶體段中存活的物件會被複制到 Survivor 區中空的記憶體分段,Survivor 區記憶體段中存活的物件如果年齡未達閾值,年齡會加1,達到閥值會被會被複制到 old 區中空的記憶體分段。如果 Survivor 空間不夠,Eden 空間的部分資料會直接晉升到老年代空間。

第五階段:處理參照佇列、軟參照、弱參照、虛參照

處理 Soft,Weak,Phantom,Final,JNI Weak 等參照。

 

最終 Eden 空間的資料為空,GC 停止工作,而目標記憶體中的物件都是連續儲存的,沒有碎片,所以複製過程可以達到記憶體整理的效果,減少碎片。

 

四、Mixed GC 混合GC

多次 Young GC 之後,當越來越多的物件晉升到老年代 old region,Old Regions 慢慢累積,直到到達閾值(InitiatingHeapOccupancyPercent,簡稱 IHOP),我們不得不對 Old Regions 做收集。這個閾值在 G1 中是根據使用者設定的 GC 停頓時間動態調整的,也可以人為干預。

對 Old Regions 的收集會同時涉及若干個 Young 和 Old Regions,因此被稱為 Mixed GC。

 

Mixed GC 很多地方都和 Young GC 類似,不同之處是:它還會選擇若干最有潛力的 Old Regions(收集垃圾的效率最高的 Regions),這些選出來要被 Evacuate 的 Region 稱為本次的 Collection Set (CSet)。

 

這裡需要注意:是一部分老年代,而不是全部老年代,可以選擇哪些 old region 進行收集,從而可以對垃圾回收的耗時時間進行控制。

 

結合Region 的設計,只要把每次的 Collection Set 規模控制在一定範圍,就能把每次收集的停頓時間軟性地控制在 MaxGCPauseMillis 以內。起初這個控制可能不太精準,隨著 JVM 的執行估算會越來越準確。

 

那來不及收集的那些 Region 呢?多來幾次就可以了。所以你在 GC 紀錄檔中會看到 continue mixed GCs 的字樣,代表分批進行的各次收集。這個過程會多次重複,直到垃圾的百分比降到 G1HeapWastePercent 以內,或者到達 G1MixedGCCountTarget 上限。

 

1、STAB和TAMS

 

在 Evacuation 之前,我們要通過並行標記來確定哪些物件是垃圾、哪些還活著。G1 中的 Concurrent Marking 是以 Region 為單位的,為了保證結果的正確性,這裡用到了 Snapshot-at-the-beginning(SATB)演演算法。

 

SATB 演演算法顧名思義是對 Marking 開始時的一個(邏輯上的)Snapshot 進行標記。為什麼要用 Snapshot 呢?下面就是一個直接標記導致問題的例子:物件 X 由於沒有被標記到而被標記為垃圾,導致 B 參照失效。

 

 

如果只是對現場情況做標記,可能會漏掉某些物件。SATB 演演算法為了解決這一問題,在修改參照 X.f = B 之前插入了一個 Write Barrier,記錄下被覆寫之前的參照地址。這些地址最終也會被 Marking 執行緒處理,從而確保了所有在 Marking 開始時的參照一定會被標記到。

 

這個 Write Barrier 虛擬碼如下:

1

2

3

t = the previous referenced address // 記錄原本的參照地址

if (t has been marked && t != NULL) // 如果地址 t 還沒來的及標記,且 t 不為 NULL

satb_enqueue(t) // 放到 SATB 的待處理佇列中,之後會去掃描這個參照

通過以上措施,SATB 確保 Marking 開始時存活的物件一定會被標記到。

 

2、Concurrent Marking

 

G1標記的過程和 CMS 中是類似的,可以看作一個優化版的 DFS:記當前已經標記到的 offset 為 cur,隨著標記的進行 cur 不斷向後推進。每當存取到地址 < cur 的物件,就對它做深度掃描,遞迴標記所有應用;反之,對於地址 > cur 的物件,只標記不掃描,等到 cur 推進到那邊的時候再去做掃描。

 

 

 

上圖中,假設當前 cur 指向物件 c,c有兩個參照:a 和 e,其中 a 的地址小於 cur,因而做了掃描;而 e 則僅僅是標記。掃描 a 的過程中又發現了物件 b,b 同樣被標記並繼續掃描。但是 b 參照的 d 在 cur 之後,所以 d 僅僅是被標記,不再繼續掃描。

 

最後一個問題是:如何處理 Concurrent Marking 中新產生的物件?因為 SATB 演演算法只保證能標記到開始時 snapshot 的物件,對於新出現的那些物件,我們可以簡單地認為它們全都是存活的,畢竟數量不是很多。

 

2、回收過程

 

G1垃圾回收週期如下圖所示:

 

 

G1的Mixed GC回收過程可以分為標記階段、清理階段和複製階段。

(1)標記階段停頓分析
  • 初始標記階段:初始標記階段是指從GC Roots出發標記全部直接子節點的過程,該階段是STW的。由於GC Roots數量不多,通常該階段耗時非常短。
  • 並行標記階段:並行標記階段是指從GC Roots開始對堆中物件進行可達性分析,找出存活物件。該階段是並行的,即應用執行緒和GC執行緒可以同時活動。並行標記耗時相對長很多,但因為不是STW,所以我們不太關心該階段耗時的長短。
  • 再標記階段:重新標記那些在並行標記階段發生變化的物件。該階段是STW的。
(2)清理階段停頓分析
  • 清理階段清點出有存活物件的分割區和沒有存活物件的分割區,該階段不會清理垃圾物件,也不會執行存活物件的複製。該階段是STW的。
(3)複製階段停頓分析
  • 複製演演算法中的轉移階段需要分配新記憶體和複製物件的成員變數。轉移階段是STW的,其中記憶體分配通常耗時非常短,但物件成員變數的複製耗時有可能較長,這是因為複製耗時與存活物件數量與物件複雜度成正比。物件越複雜,複製耗時越長。

 

四個STW過程中,初始標記因為只標記GC Roots,耗時較短。

再標記因為物件數少,耗時也較短。清理階段因為記憶體分割區數量少,耗時也較短。

轉移階段要處理所有存活的物件,耗時會較長。

因此,G1停頓時間的瓶頸主要是標記-複製中的轉移階段STW。

為什麼轉移階段不能和標記階段一樣並行執行呢?主要是G1未能解決轉移過程中準確定位物件地址的問題。

 

五、Serial Old GC

 

如果mixed GC實在無法跟上程式分配記憶體的速度,導致老年代填滿無法繼續進行Mixed GC,就會使用serial old GC(full GC)來收集整個GC heap。所以我們可以知道,G1是不提供full GC的。

 

Serial Old是Serial收集器的老年代版本,是一個單執行緒收集器,使用標記-整理演演算法。

 

1、Serial Old收集

Serial收集器過程如下:

 

 

 

 

 

優點:演演算法簡單,記憶體佔用少,CPU不用切換程序,導致上下文切換時間短,總體效率高

缺點:GC階段卡頓

 

2、G1產生FGC如何解決

  • 擴充套件記憶體
  • 提高CPU效能(回收的快,業務邏輯產生物件的速度固定,垃圾回收越快,記憶體空間越大)
  • 降低MixedGC觸發的閾值,讓MixedGC提早發生(預設是45%)

 

 

六、對比CMS 

1、G1 相比較 CMS的改進

  • 演演算法: G1 基於標記--整理演演算法, 不會產生空間碎片,在分配大物件時,不會因無法得到連續的空間,而提前觸發一次 FULL GC 。
  • 停頓時間可控: G1可以通過設定預期停頓時間(Pause Time)來控制垃圾收集時間避免應用雪崩現象。
  • 並行與並行:G1 能更充分的利用 CPU 多核環境下的硬體優勢,來縮短 stop the world 的停頓時間。

2、CMS 和 G1 的區別

  • CMS 中,堆被分為 PermGen,YoungGen,OldGen ;而 YoungGen 又分了兩個 survivo 區域。在 G1 中,堆被平均分成幾個區域 (region) ,在每個區域中,雖然也保留了新老代的概念,但是收集器是以整個區域為單位收集的。
  • G1 在回收記憶體後,會立即同時做合併空閒記憶體的工作;而 CMS ,則預設是在 STW(stop the world)的時候做。
  • G1 會在 Young GC 中使用;而 CMS 只能在 O 區使用。

 

參考資料:

https://ericfu.me/g1-garbage-collector/

https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html

https://tech.meituan.com/2016/09/23/g1.html

https://juejin.cn/post/6844904106268557320