天天看點

JVM 垃圾收集器 G1 詳解

G1 垃圾收集器

Opening words

這是一篇欠了自己很久的文章吧,一直想寫一篇關于垃圾收集器 G1 的總結文章,卻遲遲沒有下筆哈哈,本篇将深入淺出帶大家看看 G1 究竟給我們程式的垃圾收集帶來了什麼。

G1 簡介

The Garbage-First (G1) 是伺服器類型的垃圾收集器,它适用于大記憶體的垃圾收集處理并且可以控制垃圾收集 STW 暫停時間長度(當然這個時間的設定需要是合理的),在 JDK 1.7 版本 G1 得到了完全的支援。

G1 通過将活動對象從将一組或者多組區域(該區域稱為 集合集 CSet) 增量并行複制到一個或者多個不同的新的區域來實作空間壓縮來減少堆記憶體的碎片。目标是回收盡可能多的記憶體空間,是以在垃圾回收時 G1 會在滿足暫停時間期望的前提下優先挑選垃圾對象最多的區域進行回收。

G1 的一些特性:

  1. 可以和應用程式線程并發運作就像 CMS 垃圾收集器一樣。
  2. 緊湊的可使用空間,無碎片化記憶體。
  3. 可預測的 GC STW 時間。
  4. 高性能

G1 是計劃作為 CMS 的替代或者說更新的,通常來說我們都會對 G1 和 CMS 來進行比較,這樣可以更好的看出我們使用 G1 的理由。

一個差別是 G1 是壓縮收集器,其使用 Region 區域來配置設定空間,使用标記複制算法來進行垃圾的收集,避免了潛在的記憶體碎片化問題。

同時 G1 提供了可預測的垃圾收集暫停時長,允許使用者設定暫停目标,需要知道的是暫停目标并不是總能達到其是一個預估值,需要進行合理的設定。

記憶體劃分

JVM 垃圾收集器 G1 詳解

Region 區, G1 将 Java 堆劃分為多個 Region 區,JVM 最多可以有 2048 個 Region 區,一般 Region 區大小為堆大小除以 2048,即 4096M 堆大小,Region 區大小為 2M。可以通過參數

-XX:G1HeapRegionSize

來指定 Region 大小,但推薦使用預設的劃分方式。

G1 仍然保留了 CMS 的分代的概念,但是各代之間因為 Region 區不再實體隔離,即目前的 Region 區是 Eden ,在經曆垃圾回收後可能會變成其他區如 Old 或者 Survivor 等。

增加了 Humongous 巨型對象區的概念,當一個對象超過了 Region 區大小的 50% 則被認定為巨型對象,直接進入 Humongous 區,在 young gc 時 Humongous 區對象不參與回收,mixed gc 時會對該區域進行回收,以此減少巨型對象的回收頻率。

Eden 區 新生代的大小初始預設為整個堆大小的 5% ,在 JVM 運作的過程中會進行動态擴充,但其最大不會超過堆大小的 60%。可以通過參數

-XX:G1NewSizePercent=5

-XX:G1MaxNewSizePercent=60

調整該值,非必要不建議修改。

Humongous 文檔

垃圾收集階段

除了并發失敗所導緻的 FULL GC 即停止應用線程所有操作,專注于垃圾收集外,G1 分以下兩種正常 GC 收集模式

Young Garbage Collections 年輕代垃圾收集

G1 的垃圾收集滿足了在 eden 區進行記憶體配置設定的請求,young gc 并不是在 eden 區滿時就馬上觸發,G1 會計算評估目前收集垃圾所消耗的時間,如果該時間遠遠小于設定的

-XX:MaxGCPauseMillis

最大停頓時間,G1 将嘗試擴充 eden 區域,直至某次計算接近該值則觸發 gc。

在 young gc 的過程中會同時清理前一次垃圾收集後形成的 eden 區和 survivor 區,在 eden 區和 survivor 的幸存者即非垃圾對象将被複制或者疏散到新的區域,配置設定到的目的區域取決于對象的年齡(這就和 CMS 一樣),那些足夠老化的對象将被配置設定到老年代,否則其将被移到 survivor 區并被添加到下一次垃圾回收的 CSet 中,其餘垃圾對象将被直接清除(可達性分析)。

Mixed Garbage Collections 混合垃圾收集

在成功完成并發标記周期後,如果發現 young gc 無法回收出空餘記憶體 G1 将切換到 mixed 混合垃圾收集方式,在此過程 G1 可以選擇将 old 區添加到将要收集的 eden 和 survivor 區域集合中。G1 在收集到足夠數量的 old 區域後(多次 mixed gc 後)會恢複為 young gc 。

标記周期階段

  1. 初始标記 Initial marking phase【STW】:初始标記會标記根引用,并裝載正常的 young gc。該階段為 STW。
  2. 根區域掃描 Root region scanning phase:G1 垃圾收集器掃描在初始标記階段标記的 survivor 區域,以擷取老年代的引用,并标記引用的對象。此階段與應用程式同時進行。
  3. 并發标記 Concurrent marking phase:在整個堆中找到可達對象。并發标記階段就是從 GC Roots 的直接關聯對象開始周遊整個對象圖的過程, 這個過程耗時較長但是不需要停頓應用線程, 可以與垃圾收集線程一起并發運作。因為并行可能會有導緻已經标記過的對象狀态發生改變。因而需要重新标記
  4. 重新标記 Remark phase【STW】:該階段将 STW 來幫助最後完成标記,該階段将使用 **原始快照(Snapshot At The Beginning,SATB) ** SATB buffers 追蹤未掃描對象并進行标記處理。
  5. 清理階段 Cleanup phase【STW】:在這個最後階段 G1 将進行 STW 并清洗 RSet,G1 将根據設定的目标暫停時間來制定清理計劃,選擇回收效益最高的區域進行回收。回收算法采用複制算法。此過程将标記出完全空閑的區域,并且将判斷是否需要切換為 mixed gc
新生代可以引入記錄集(Remember Set)的資料結構(記錄從非收集區到收集區的指針集合),避免把整個老年代加入GCRoots掃描範圍。
JVM 垃圾收集器 G1 詳解

G1 的 Full GC

當 mixed gc 失敗後将觸發 Concurrent Mode Failure 并發失敗,執行 full gc,該過程将啟用單線程執行垃圾收集,暫停所有應用線程。

重要的配置

Option and Default Value Option
-XX:G1HeapRegionSize=n 設定 G1 region 區的大小,大小在 1MB 到 32MB 。值應該是 2 的 n 次幂,基于最小 java 堆大小最多劃分 2048 個 region 區。
-XX:MaxGCPauseMillis=200 最大 GC 停頓時間 機關毫秒
-XX:G1NewSizePercent=5 年輕代最小值,占總堆大小的百分比,預設為 5%
-XX:G1MaxNewSizePercent=60 年輕代最大值,占總堆大小的百分比,預設為 60%
-XX:ParallelGCThreads=n 設定 STW 工作線程的值。該值設定預設為邏輯處理器的數量。最多為 8。如果有八個以上的邏輯處理器,請将 n 的值設定為邏輯處理器的大約 5/8。這在大多數情況下都有效,但較大的 SPARC 系統除外,其中 n 的值大約為邏輯處理器的 5/16。
-XX:ConcGCThreads=n 設定并行标記線程的數量。設定

n

為并行垃圾回收線程 (ParallelGCThreads) 數量的大約 1/4。
-XX:InitiatingHeapOccupancyPercent=45 設定觸發标記周期的 Java 堆占用門檻值。預設占用率為整個 Java 堆的 45%。
-XX:G1MixedGCLiveThresholdPercent=85 設定要包含在混合垃圾回收周期中的舊區域的占用門檻值。預設占用率為 85%。
-XX:G1HeapWastePercent=5 設定願意浪費的堆百分比。當可回收百分比小于堆浪費百分比時,Java HotSpot VM 不會啟動混合垃圾收集周期。預設值為 5%。
-XX:G1MixedGCCountTarget=8 在标記周期後設定混合垃圾收集的目标數量,以收集最多具有

G1MixedGCLIveThresholdPercent

實時資料的舊區域。預設為 8 個混合垃圾回收。混合集合的目标是在此目标數量内。
-XX:G1OldCSetRegionThresholdPercent=10 設定混合垃圾收集周期中要收集的舊區域數量的上限。預設值為 Java 堆的 10%。
-XX:G1ReservePercent=10 設定保留記憶體的百分比以保持空閑狀态,以降低空間溢出的風險。預設值為 10%。當您增加或減少百分比時,請確定将總 Java 堆調整相同的數量。