1. 程式人生 > >G1垃圾回收器調優

G1垃圾回收器調優

瞭解如何針對評估、分析和效能來調整和調優 G1 GC。

2013 年 8 月釋出

垃圾優先型垃圾回收器 (G1 GC) 是適用於 Java HotSpot VM 的低暫停、伺服器風格的分代式垃圾回收器。G1 GC 使用併發和並行階段實現其目標暫停時間,並保持良好的吞吐量。當 G1 GC 確定有必要進行垃圾回收時,它會先收集存活資料最少的區域(垃圾優先)。

垃圾回收器 (GC) 是一個記憶體管理工具。G1 GC 通過以下操作實現自動記憶體管理:

  • 將物件分配給年輕代,並將老化物件晉升到老年代。
  • 通過併發(並行)標記階段,查詢老年代中的存活物件。當總的 Java 堆佔用率超過預設的閾值時,Java HotSpot VM 將觸發標記階段。
  • 通過並行複製壓縮存活物件,恢復空閒記憶體。

現在,我們來看看如何針對評估、分析和效能來調整和調優 G1 GC。我們假定您對 Java 垃圾回收有基本的瞭解。

G1 GC 是區域化、分代式垃圾回收器,這意味著 Java 物件堆(堆)被劃分成大小相同的若干區域。啟動時,Java 虛擬機器 (JVM) 會設定區域大小。區域大小從 1 MB 到 32 MB 不等,具體取決於堆大小。目標是產生不超過 2048 個區域。Eden、存活空間和老年代是一系列不連續的邏輯區域。

G1 GC 有一個力求達到的暫停時間目標(軟實時)。在年輕代回收期間,G1 GC 會調整其年輕代空間(eden 和存活空間大小)以滿足軟實時目標。在混合回收期間,G1 GC 會根據混合垃圾回收的目標次數調整所回收的舊區域數量,並調整堆的每個區域中存活物件的百分比,以及總體可接受的堆廢物百分比。

G1 GC 將一組或多組區域(稱為回收集 (CSet))中的存活物件以增量、並行的方式複製到不同的新區域來實現壓縮,從而減少堆碎片。目標是從可回收空間最多的區域開始,儘可能回收更多的堆空間,同時儘可能不超出暫停時間目標(垃圾優先)。

G1 GC 使用獨立的記憶集 (RSet) 跟蹤對區域的引用。獨立的 RSet 可以並行、獨立地回收區域,因為只需要對區域(而不是整個堆)的 RSet 進行區域引用掃描。G1 GC 使用後寫屏障記錄堆的更改和更新 RSet。

垃圾回收階段

除了構成停頓 (STW) 年輕代和混合垃圾回收的疏散暫停(如下所述),G1 GC 還具有並行、併發和多階段標記週期。G1 GC 使用初始快照 (SATB) 演算法,在標記週期之初為堆中的存活物件集建立快照。存活物件集包括快照中的存活物件,以及標記週期開始以來所分配的物件。G1 GC 標記演算法使用預寫屏障記錄和標記作為邏輯快照一部分的物件。

年輕代垃圾回收

G1 GC 可滿足來自被新增到 eden 區域集的區域的大多數分配請求。在年輕代垃圾回收期間,G1 GC 會同時回收 eden 區域和上次垃圾回收的存活區域。Eden 和存活區的存活物件將被複制或疏散到新的區域集。特定物件的目標區域取決於物件的年齡;足夠老的物件疏散到老年代區域(也就晉級);否則疏散到存活區,並將包含在下一次年輕代或混合垃圾回收的 CSet 中。

混合垃圾回收

成功完成併發標記週期後,G1 GC 從執行年輕代垃圾回收切換為執行混合垃圾回收。在混合垃圾回收期間,G1 GC 可以將一些舊的區域新增到 eden 和存活區供將來回收。所新增舊區域的確切數量由一系列標誌控制。關於標誌的資訊,將在後面討論(請參見“掌握混合垃圾回收”)。G1 GC 回收了足夠的舊區域後(經過多次混合垃圾回收),G1 將恢復執行年輕代垃圾回收,直到下一個標記週期完成。

標記週期的各個階段

標記週期包括以下幾個階段:

  • 初始標記階段:在此階段,G1 GC 對根進行標記。該階段與常規的 (STW) 年輕代垃圾回收密切相關。
  • 根區域掃描階段:G1 GC 在初始標記的存活區掃描對老年代的引用,並標記被引用的物件。該階段與應用程式(非 STW)同時執行,並且只有完成該階段後,才能開始下一次 STW 年輕代垃圾回收。
  • 併發標記階段:G1 GC 在整個堆中查詢可訪問的(存活的)物件。該階段與應用程式同時執行,可以被 STW 年輕代垃圾回收中斷。
  • 重新標記階段:該階段是 STW 回收,幫助完成標記週期。G1 GC 清空 SATB 緩衝區,跟蹤未被訪問的存活物件,並執行引用處理。
  • 清理階段:在這個最後階段,G1 GC 執行統計和 RSet 淨化的 STW 操作。在統計期間,G1 GC 會識別完全空閒的區域和可供進行混合垃圾回收的區域。清理階段在將空白區域重置並返回到空閒列表時為部分併發。

重要的預設值

G1 GC 是自適應的垃圾回收器,提供了若干預設設定,使其無需修改即可高效地工作。以下是重要選項及其預設值的列表。此列表適用於最新的 Java HotSpot VM build 24。您可以通過在 JVM 命令列輸入下列選項和已更改的設定,根據您的應用程式效能需求調整和調優 G1 GC。

  • -XX:G1HeapRegionSize=n
    

    設定的 G1 區域的大小。值是 2 的冪,範圍是 1 MB 到 32 MB 之間。目標是根據最小的 Java 堆大小劃分出約 2048 個區域。

  • -XX:MaxGCPauseMillis=200
    

    為所需的最長暫停時間設定目標值。預設值是 200 毫秒。指定的值不適用於您的堆大小。

  • -XX:G1NewSizePercent=5
    

    設定要用作年輕代大小最小值的堆百分比。預設值是 Java 堆的 5%。這是一個實驗性的標誌。有關示例,請參見“如何解鎖實驗性虛擬機器標誌”。此設定取代了 -XX:DefaultMinNewGenPercent 設定。Java HotSpot VM build 23 中沒有此設定。

  • -XX:G1MaxNewSizePercent=60
    

    設定要用作年輕代大小最大值的堆大小百分比。預設值是 Java 堆的 60%。這是一個實驗性的標誌。有關示例,請參見“如何解鎖實驗性虛擬機器標誌”。此設定取代了 -XX:DefaultMaxNewGenPercent 設定。Java HotSpot VM build 23 中沒有此設定。

  • -XX:ParallelGCThreads=n
    

    設定 STW 工作執行緒數的值。將 n 的值設定為邏輯處理器的數量。n 的值與邏輯處理器的數量相同,最多為 8。

    如果邏輯處理器不止八個,則將 n 的值設定為邏輯處理器數的 5/8 左右。這適用於大多數情況,除非是較大的 SPARC 系統,其中 n 的值可以是邏輯處理器數的 5/16 左右。

  • -XX:ConcGCThreads=n
    

    設定並行標記的執行緒數。將 n 設定為並行垃圾回收執行緒數 (ParallelGCThreads) 的 1/4 左右。

  • -XX:InitiatingHeapOccupancyPercent=45
    

    設定觸發標記週期的 Java 堆佔用率閾值。預設佔用率是整個 Java 堆的 45%。

  • -XX:G1MixedGCLiveThresholdPercent=65
    

    為混合垃圾回收週期中要包括的舊區域設定佔用率閾值。預設佔用率為 65%。這是一個實驗性的標誌。有關示例,請參見“如何解鎖實驗性虛擬機器標誌”。此設定取代了 -XX:G1OldCSetRegionLiveThresholdPercent 設定。Java HotSpot VM build 23 中沒有此設定。

  • -XX:G1HeapWastePercent=10
    

    設定您願意浪費的堆百分比。如果可回收百分比小於堆廢物百分比,Java HotSpot VM 不會啟動混合垃圾回收週期。預設值是 10%。Java HotSpot VM build 23 中沒有此設定。

  • -XX:G1MixedGCCountTarget=8
    

    設定標記週期完成後,對存活資料上限為 G1MixedGCLIveThresholdPercent 的舊區域執行混合垃圾回收的目標次數。預設值是 8 次混合垃圾回收。混合回收的目標是要控制在此目標次數以內。Java HotSpot VM build 23 中沒有此設定。

  • -XX:G1OldCSetRegionThresholdPercent=10
    

    設定混合垃圾回收期間要回收的最大舊區域數。預設值是 Java 堆的 10%。Java HotSpot VM build 23 中沒有此設定。

  • -XX:G1ReservePercent=10
    

    設定作為空閒空間的預留記憶體百分比,以降低目標空間溢位的風險。預設值是 10%。增加或減少百分比時,請確保對總的 Java 堆調整相同的量。Java HotSpot VM build 23 中沒有此設定。

如何解鎖實驗性虛擬機器標誌

要更改實驗性標誌的值,必須先對其解鎖。解鎖方法是:在命令列中的實驗性標誌前,顯式地設定 -XX:+UnlockExperimentalVMOptions。例如:

> java -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=10 -XX:G1MaxNewSizePercent=75  G1test.jar

建議

評估和微調 G1 GC 時,請記住以下建議:

  • 年輕代大小:避免使用 -Xmn 選項或 -XX:NewRatio 等其他相關選項顯式設定年輕代大小。固定年輕代的大小會覆蓋暫停時間目標。
  • 暫停時間目標:每當對垃圾回收進行評估或調優時,都會涉及到延遲與吞吐量的權衡。G1 GC 是增量垃圾回收器,暫停統一,同時應用程式執行緒的開銷也更多。G1 GC 的吞吐量目標是 90% 的應用程式時間和 10%的垃圾回收時間。如果將其與 Java HotSpot VM 的吞吐量回收器相比較,目標則是 99% 的應用程式時間和 1% 的垃圾回收時間。因此,當您評估 G1 GC 的吞吐量時,暫停時間目標不要太嚴苛。目標太過嚴苛表示您願意承受更多的垃圾回收開銷,而這會直接影響到吞吐量。當您評估 G1 GC 的延遲時,請設定所需的(軟)實時目標,G1 GC 會盡量滿足。副作用是,吞吐量可能會受到影響。
  • 掌握混合垃圾回收:當您調優混合垃圾回收時,請嘗試以下選項。有關這些選項的資訊,請參見“重要的預設值”:

    • -XX:InitiatingHeapOccupancyPercent
      用於更改標記閾值。
    • -XX:G1MixedGCLiveThresholdPercent 和 -XX:G1HeapWastePercent
      當您想要更改混合垃圾回收決定時。
    • -XX:G1MixedGCCountTarget 和 -XX:G1OldCSetRegionThresholdPercent
      當您想要調整舊區域的 CSet 時。

有關溢位和用盡的日誌訊息

當您在日誌中看到目標空間溢位/用盡的訊息時,意味著 G1 GC 沒有足夠的記憶體,供存活者和/或晉升物件使用。Java 堆不能擴充套件,因為已達到最大值。示例訊息:

924.897: [GC pause (G1 Evacuation Pause) (mixed) (to-space exhausted), 0.1957310 secs]



924.897:[GC pause (G1 Evacuation Pause) (mixed) (to-space overflow), 0.1957310 secs]

要緩解此問題,請嘗試以下調整:

增加 -XX:G1ReservePercent 選項的值(並相應增加總的堆大小),為“目標空間”增加預留記憶體量。

通過減少 -XX:InitiatingHeapOccupancyPercent 提前啟動標記週期。

您也可以通過增加 -XX:ConcGCThreads 選項的值來增加並行標記執行緒的數目。

有關這些選項的描述,請參見“重要的預設值”。

巨型物件和巨型分配

對於 G1 GC,任何超過區域一半大小的物件都被視為“巨型物件”。此類物件直接被分配到老年代中的“巨型區域”。這些巨型區域是一個連續的區域集。StartsHumongous 標記該連續集的開始,ContinuesHumongous 標記它的延續。

在分配任何巨型區域之前,會檢查標記閾值,如有必要,還會啟動一個併發週期。

在清理階段或完整的垃圾回收週期內,標記週期結束時會清理死亡的巨型物件。

為了減少複製開銷,巨型物件未包括在疏散暫停中。完整的垃圾回收週期會對巨型物件進行壓縮。

由於每個 StartsHumongous 和 ContinuesHumongous 區域集只包含一個巨型物件,所以沒有使用巨型物件的終點與上個區域的終點之間的空間(即巨型物件所跨的空間)。如果物件只是略大於堆區域大小的倍數,則此類未使用的空間可能會導致堆碎片化。

如果巨型分配導致連續的併發週期,並且此類分配導致老年代碎片化,請增加 -XX:G1HeapRegionSize,這樣一來,之前的巨型物件就不再是巨型物件了,而是採用常規的分配路徑。

總結

G1 GC 是區域化、並行-併發、增量式垃圾回收器,相比其他 HotSpot 垃圾回收器,可提供更多可預測的暫停。增量的特性使 G1 GC 適用於更大的堆,在最壞的情況下仍能提供不錯的響應。G1 GC 的自適應特性使 JVM 命令列只需要軟實時暫停時間目標的最大值以及 Java 堆大小的最大值和最小值,即可開始工作。

另請參見

關於作者

Monica Beckwith,Oracle 技術團隊的主要成員,是 Java HotSpot VM 的垃圾優先型垃圾回收器的效能帶頭人。她在效能和架構領域已從業 10 年有餘。在加入 Oracle 和 Sun Microsystems 之前,Monica 曾是 Spansion Inc. 的效能帶頭人。Monica 參與了許多行業級 Java 基準測試標準的制定,為 Java HotSpot 虛擬機器找出改進機會是她永恆的目標。

分享交流

請在 FacebookTwitter 和 Oracle Java 部落格上加入 Java 社群對話!