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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章