參考文檔:https://juejin.im/post/5b6b986c6fb9a04fd1603f4a#heading-18
G1原理及調優
1 G1簡介
G1(Garbage-First)是一款面向服務器的垃圾收集器,支持新生代和老年代空間的垃圾收集,主要針對配備多核處理器及大容量內存的機器,G1最主要的設計目標是: 實現可預期及可配置的STW停頓時間
2 G1堆空間划分

- Region
為實現大內存空間的低停頓時間的回收,將划分為多個大小相等的Region。每個小堆區都可能是 Eden區,Survivor區或者Old區,但是在同一時刻只能屬於某個代
在邏輯上, 所有的Eden區和Survivor區合起來就是新生代,所有的Old區合起來就是老年代,且新生代和老年代各自的內存Region區域由G1自動控制,不斷變動
- 巨型對象
當對象大小超過Region的一半,則認為是巨型對象(Humongous Object),直接被分配到老年代的巨型對象區(Humongous regions),這些巨型區域是一個連續的區域集,每一個Region中最多有一個巨型對象,巨型對象可以占多個Region
G1把堆內存划分成一個個Region的意義在於:
- 每次GC不必都去處理整個堆空間,而是每次只處理一部分Region,實現大容量內存的GC
- 通過計算每個Region的回收價值,包括回收所需時間、可回收空間,在有限時間內盡可能回收更多的內存,把垃圾回收造成的停頓時間控制在預期配置的時間范圍內,這也是G1名稱的由來: garbage-first
3 G1工作模式
針對新生代和老年代,G1提供2種GC模式,Young GC和Mixed GC,兩種會導致Stop The World
-
Young GC 當新生代的空間不足時,G1觸發Young GC回收新生代空間 Young GC主要是對Eden區進行GC,它在Eden空間耗盡時觸發,基於分代回收思想和復制算法,每次Young GC都會選定所有新生代的Region,同時計算下次Young GC所需的Eden區和Survivor區的空間,動態調整新生代所占Region個數來控制Young GC開銷
-
Mixed GC 當老年代空間達到閾值會觸發Mixed GC,選定所有新生代里的Region,根據全局並發標記階段(下面介紹到)統計得出收集收益高的若干老年代 Region。在用戶指定的開銷目標范圍內,盡可能選擇收益高的老年代Region進行GC,通過選擇哪些老年代Region和選擇多少Region來控制Mixed GC開銷
-
G1的正常處理流程中沒有Full GC,只有在垃圾回收處理不過來(或者主動觸發)時才會出現, G1的Full GC就是單線程執行的Serial old gc,會導致非常長的STW,是調優的重點,需要盡量避免Full GC,常見原因如下:
4 全局並發標記
全局並發標記主要是為Mixed GC計算找出回收收益較高的Region區域,具體分為5個階段
-
階段 1: 初始標記(Initial Mark) 暫停所有應用線程(STW),並發地進行標記從 GC Root 開始直接可達的對象(原生棧對象、全局對象、JNI 對象),當達到觸發條件時,G1 並不會立即發起並發標記周期,而是等待下一次新生代收集,利用新生代收集的 STW 時間段,完成初始標記,這種方式稱為借道(Piggybacking)
-
階段 2: 根區域掃描(Root Region Scan) 在初始標記暫停結束后,新生代收集也完成的對象復制到 Survivor 的工作,應用線程開始活躍起來; 此時為了保證標記算法的正確性,所有新復制到 Survivor 分區的對象,需要找出哪些對象存在對老年代對象的引用,把這些對象標記成根(Root); 這個過程稱為根分區掃描(Root Region Scanning),同時掃描的 Suvivor 分區也被稱為根分區(Root Region); 根分區掃描必須在下一次新生代垃圾收集啟動前完成(接下來並發標記的過程中,可能會被若干次新生代垃圾收集打斷),因為每次 GC 會產生新的存活對象集合
-
階段 3: 並發標記(Concurrent Marking) 標記線程與應用程序線程並行執行,標記各個堆中Region的存活對象信息,這個步驟可能被新的 Young GC 打斷 所有的標記任務必須在堆滿前就完成掃描,如果並發標記耗時很長,那么有可能在並發標記過程中,又經歷了幾次新生代收集
-
階段 4: 再次標記(Remark) 和CMS類似暫停所有應用線程(STW),以完成標記過程短暫地停止應用線程, 標記在並發標記階段發生變化的對象,和所有未被標記的存活對象,同時完成存活數據計算
-
階段 5: 清理(Cleanup) 為即將到來的轉移階段做准備, 此階段也為下一次標記執行所有必需的整理計算工作:
- 整理更新每個Region各自的RSet(remember set,HashMap結構,記錄有哪些老年代對象指向本Region,key為指向本Region的對象的引用,value為指向本Region的具體Card區域,通過RSet可以確定Region中對象存活信息,避免全堆掃描)
- 回收不包含存活對象的Region
- 統計計算回收收益高(基於釋放空間和暫停目標)的老年代分區集合
5 G1調優注意點
Full GC問題
G1的正常處理流程中沒有Full GC,只有在垃圾回收處理不過來(或者主動觸發)時才會出現, G1的Full GC就是單線程執行的Serial old gc,會導致非常長的STW,是調優的重點,需要盡量避免Full GC,常見原因如下:
- 程序主動執行System.gc()
- 全局並發標記期間老年代空間被填滿(並發模式失敗)
- Mixed GC期間老年代空間被填滿(晉升失敗)
- Young GC時Survivor空間和老年代沒有足夠空間容納存活對象
類似CMS,常見的解決是:
- 增大-XX:ConcGCThreads=n 選項增加並發標記線程的數量,或者STW期間並行線程的數量:-XX:ParallelGCThreads=n
- 減小-XX:InitiatingHeapOccupancyPercent 提前啟動標記周期
- 增大預留內存 -XX:G1ReservePercent=n ,默認值是10,代表使用10%的堆內存為預留內存,當Survivor區域沒有足夠空間容納新晉升對象時會嘗試使用預留內存
巨型對象分配
巨型對象區中的每個Region中包含一個巨型對象,剩余空間不再利用,導致空間碎片化,當G1沒有合適空間分配巨型對象時,G1會啟動串行Full GC來釋放空間。可以通過增加 -XX:G1HeapRegionSize來增大Region大小,這樣一來,相當一部分的巨型對象就不再是巨型對象了,而是采用普通的分配方式
不要設置Young區的大小
原因是為了盡量滿足目標停頓時間,邏輯上的Young區會進行動態調整。如果設置了大小,則會覆蓋掉並且會禁用掉對停頓時間的控制
平均響應時間設置
使用應用的平均響應時間作為參考來設置MaxGCPauseMillis,JVM會盡量去滿足該條件,可能是90%的請求或者更多的響應時間在這之內, 但是並不代表是所有的請求都能滿足,平均響應時間設置過小會導致頻繁GC.
參考文檔:https://juejin.im/post/5b6b986c6fb9a04fd1603f4a#heading-18

