1. 程式人生 > 其它 >2021-06-13:G1垃圾回收器

2021-06-13:G1垃圾回收器

原文連結 2021-06-13:G1垃圾回收器

1、為啥需要G1

在 G1 出來之前,一般系統都是使用 ParNew + CMS。而不管是 ParNew 還是 CMS,對於新生代和老年代都是使用滿了再進行gc,那麼如果我們的機器配置了60G的記憶體,新生代和老年代的比例是1:2,那麼老年代可以去到40G,那麼進行垃圾回收時,即使CMS的最後一個階段是併發清理,但是由於記憶體很大,那麼對幾十個g的記憶體進行回收,還是會耗費非常長的時間,併發清理階段,即使GC執行緒和系統執行緒並行,但是由於GC執行緒會長時間進行垃圾回收,那麼就會長時間佔用系統資源,導致系統無法處理更多的使用者請求。

即:ParNew + CMS無法做到軟實時,即無法做到將GC的停頓大致控制在某個閾值以內。
而在 G1 垃圾回收器中,我們可以利用引數-XX: MaxGCPauseMilllis

設定垃圾收集器最大停頓時間

2、GC重要概念:Region & Card & Remember Set

在 JVM 中,堆一般被分為 Eden、兩Survivor和老年代,在Java程序啟動時,每個區的記憶體是固定分配好的。

而在 G1 中,最核心的三個概念是:Region、Card 和 Remember Set。

2.1 Region

G1 垃圾收集器將堆記憶體空間分成等分的 Region,物理上不一定連續,邏輯上構成連續的堆地址空間;一個 Region 由多個 Card 組成,一個 Card 可以存放多個物件。

1、所謂 Card 就是表示一小塊(512 bytes)的記憶體空間,這裡面很可能存在不止一個物件
2、預設將堆記憶體分成2048個Region

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

2.2 Remember Set (RSet)

每個 Region 會有自己對應的 Remember Set,主要是記錄哪些記憶體區域中存在對當前 Region 中物件的引用。

注意 Remember Set 不是直接記錄物件地址,而是記錄了那些物件所在的 Card 編號。

但是這已經足夠了:當我們需要確定當前 Region 有哪些物件存在外部引用時(這些物件是可達的,不能被回收),只要掃描一下這塊 Card 中的所有物件即可,這比掃描所有存活物件要容易得多。

3、G1的分代

G1 會從邏輯上將 Region 分成 Young、Old 等不同的分代。

在經典的記憶體佈局中,各代的記憶體區域是完全分開的,而 G1 中的分代只是 Region 的一個動態標誌。

各個 Region 的所屬的分代是隨著 GC 的進行而不斷變化的,甚至各個代有多少 Region 這個比例也是隨時調整的。

分代容量的JVM引數

-XX:G1NewSizePercent:設定新生代初始佔比的。

預設5%

-XX:G1MaxNewSizePercent:設定新生代最大佔比

在系統執行中,JVM其實會不停的給新生代增加更多的Region,但是最多新生代的佔比不會超過60%(預設值)

設定Eden和Survivor佔比還是以前的引數:-XX:SurvivorRatio

4、G1中的GC

分代模式下的G1垃圾回收分為兩種:Young gc 和 Mixed GC。

上面提到,我們可以利用引數來設定期望的GC停頓時長,G1 是利用 Collection Set(CSet) 這個概念,G1 會根據配置的-XX: MaxGCPauseMilllis引數來控制 CSet 的大小,CSet 會控制存放可回收的 Region 數量。

在進行垃圾回收時,Young Regions 一定會被放到待收集的 Regions 集合(Collection Set)中,因為新生代中得物件大部分都是壽命比較短得。
由於 Young Regions 一定會被收集,所以 RSet 的維護工作不需要考慮新生代中物件的引用修改,只關心 old-to-young 和 old-to-old 的引用),當 Young Region 上發生垃圾時我們再去掃描並構建出它的 RSet 即可。

4.1 Young GC

Young GC 只會涉及到新生代的N個 Region,它將 Eden Region 中存活的物件移動到一個或多個新分配的 Survivor Region,之前的 Eden Region 就被歸還到 Free list,供以後的新物件分配使用。

當區域中物件的 Survive 次數超過閾值(引數:-XX:MaxTenuringThreshold)時,Survivor Regions 的物件被移動到 Old Regions;否則和 Eden 的物件一樣,繼續留在 Survivor Regions 裡。

多次 Young GC 之後,Old Regions 慢慢累積,直到到達閾值(-XX:InitiatingHeapOccupancyPercent,簡稱 IHOP,預設45%),我們不得不對 Old Regions 做收集。這個閾值在 G1 中是根據使用者設定的 GC 停頓時間動態調整的,也可以人為干預。

4.2 Mixed GC

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

Mixed GC 的重要性不言而喻:Old Regions 的垃圾就是在這個階段被收集掉的,也正是因為這樣,Mixed GC 是工作量最為繁重的一個環節,如果不加以控制,就會像 CMS 一樣發生長時間的 Full GC 停頓。

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

5、併發標記原理

在進行垃圾回收之前,G1 要通過併發標記來確定哪些物件是垃圾、哪些還活著。G1 中得併發標記階段是以 Region 為單位的,為了保證結果的正確性,這裡用到了 Snapshot-at-the-beginning(SATB)演算法。

SATB 演算法顧名思義是對標記開始時的一個(邏輯上的)快照進行標記。

開啟併發標記後,由於是基於快照做的;所以期間如果需要修改引用,會記錄引用地址,防止會漏掉。

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

基於 cur 指標實現併發標記


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

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