八、G1收集器-調優(翻譯自官方文檔)


      本節介紹,為了應用的評估,分析和性能,如何調節G1收集器。
       像在G1收集器那一節描述的,G1收集器是分代的和region化的,也就是整個堆內存被分為一系列大小相等的region。在啟動時,JVM設置region的大小,根據堆大小的不同,region的大小可以在1MB32MB之間變動,region的數量最多不超過2048個。Eden區、Survivor區、老年代是這些region的邏輯集合,它們並不是連續的。
      G1收集器可以設置暫停時間目標,收集器將盡可能的達到這個目標(軟實時)。在進行年輕代收集時,G1調整它的年輕代(Eden 和 Survivor)以達到軟實時的目的;在混合收集時,G1根據三個指標來調整它收集的老年代region的數量,一是:混合收集的目標數量,二是:堆中每個region中存活對象的百分比,三是:整個堆可允許的堆內存浪費百分比。
       G1將存活對象從一組或多組region中,通過增量並行的方式,復制到一個或多個不同的新region來減少堆內存碎片,以達到壓縮的目的。目標是,從那些包含最可能多的回收空間的region開始,盡可能多地回收堆空間,同時嘗試不超過暫停時間目標(垃圾優先)。
       G1收集器使用獨立的Remembered Sets(RSets)來跟蹤對region的引用。獨立的RSets支持並行和獨立的region集合,因為只有相關region的RSet必須被掃描,而不是整個堆。G1使用post-write barrier(寫后屏障)來記錄對堆的更改並更新RSets。

1、垃圾收集的階段

      除了evacuation(疏散、轉移)階段(這個階段包含年輕代的STW和混合垃圾收集),G1收集器也具有並行、並發和多階段的標記回收階段,G1收集器使用snapshot-at-the-beginning (SATB)算法,這種算法是在標記開始時獲取堆中存活對象的一個快照。存活對象集還包括自標記周期開始以來分配的對象。G1標記算法使用pre-write barrier來記錄和標記作為邏輯快照的那部分對象。

2、年輕代垃圾收集

       G1收集器使用eden region來滿足大多數內存分配需求,在年輕代的垃圾收集過程中,G1收集器,依賴上一次的垃圾收集,來獲取需要被收集那些eden regionsurvivor regioneden regionsurvivor region中的存活對象被復制或者轉移到新的region集合中,對於一個特定的對象,它的目標region取決於對象的年齡,年齡充分大的對象將被轉移到old region中(也就是對象被提升為老年代對象);否則,對象將被轉移到survivor region中,並且也被記錄在下一次垃圾收集(可能是年輕代垃圾收集,也可能是混合垃圾收集)的CSet中。

3、混合垃圾收集

       在成功完成並發標記之后,G1收集器會從年輕代垃圾收集切換到混合垃圾收集。在混合垃圾收集過程中,G1選擇性的將一些old region添加到將被收集的eden regionsurvivor region的集合中。具體有多少old region被添加到待收集集合中,是由一個數字標識控制的(參見下面的建議一節)。在G1收集了足夠數量的old region之后(通過多次的混合收集),G1恢復到年輕代垃圾收集,直到下一個標記周期完成。

4、標記周期中的各個階段

      標記周期分為以下幾個階段:
      1.初始標記(Initial marking):初始標記只標記GC Roots能直接關聯到的對象,這個階段是以正常的年輕代垃圾收集為基礎的,需要STW;
      2.root region掃描(Root region scanning):G1掃描在初始標記階段被標記的survivor region,從中獲取old region的引用並且標記相關的對象,這個階段和應用程序並發運行,不需要STW,並且,該階段必須在下一次需要STW的年輕代垃圾收集之前完成;
      3.並發標記(Concurrent marking):G1收集器遍歷整個堆以尋找可達對象,這個階段和應用程序並發執行,並且可以被需要STW是年輕代垃圾收集中斷;
      4.重新標記(Remark):這個階段需要STW,該階段是為了修正在並發標記期間因應用程序繼續運作而導致標記產生變動的那一部分標記記錄。G1釋放SATB緩沖區,跟蹤為訪問的存活對象,並執行引用處理;
      5.清理階段(Cleanup):在最后階段,G1會執行計算和RSet的清理工作(需要STW),在計算期間,G1標識完全空閑的region和混合垃圾收集的候選region。清理階段,重置空閑region並將其返回到空閑列表,這個過程是部分並發的。

5、重要的默認值

      G1收集器是一個自適應的垃圾收集器,它的默認值無需修改就可以高效的工作,下表列出了一下重要的參數和其默認值:

參數及其默認值 說明
-XX:G1HeapRegionSize = n 設置region的大小,該值為2的冪,范圍為1MB到32 MB,目標是根據最小Java堆大小,將堆分成大約2048個region
-XX:MaxGCPauseMillis = 200 設置最大停頓時間,默認值為200毫秒。
-XX:G1NewSizePercent = 5 設置年輕代占整個堆的最小百分比,默認值是5,這是個實驗參數,如果設置該值,將覆蓋默認參數 -XX:DefaultMinNewGenPercent。(JVM build > 23)
-XX:G1MaxNewSizePercent = 60 設置年輕代占整個堆的最大百分比,默認值是60,這是個實驗參數,如果設置該值,將覆蓋默認參數 -XX:DefaultMaxNewGenPercent。(JVM build > 23)
-XX:ParallelGCThreads = n 設置STW的垃圾收集線程數,當邏輯處理器數量小於8時,n的值與邏輯處理器數量相同;如果邏輯處理器數量大於8個,則n的值大約為邏輯處理器數量的5/8,大多數情況下是這樣,除了較大的SPARC系統,其中n的值約為邏輯處理器的5/16。
-XX:ConcGCThreads = n 設置並行標記線程的數量,設置n大約為ParallelGCThreads參數值的1/4。
-XX:InitiatingHeapOccupancyPercent = 45 設置觸發標記周期的Java堆占用閾值,默認值為45。
-XX:G1MixedGCLiveThresholdPercent = 85 設置要包含在混合垃圾收集周期中的old region的占用閾值,默認值為85。這是個實驗參數,如果設置該值,將覆蓋默認參數 -XX:G1OldCSetRegionLiveThresholdPercent。(JVM build > 23)
-XX:G1HeapWastePercent=5 設置浪費的堆內存百分比,當可回收百分比小於浪費百分比時,JVM就不會啟動混合垃圾收。(就是設置垃圾對象占用內存百分比的最大值)。(JVM build > 23)
-XX:G1MixedGCCountTarget = 8 設置在標記周期完成之后混合收集的數量,以維持old region(也就是老年代)中,最多有G1MixedGCLiveThresholdPercent的存活對象。默認值為8,混合收集的數量將維持在這個值之內。(JVM build > 23)
-XX:G1OldCSetRegionThresholdPercent = 10 設置在一次混合收集中被收集的old region數量的上線,默認值是整個堆的10%。(JVM build > 23)
-XX:G1ReservePercent = 10 設置預留空閑內存百分比,以降低內存溢出的風險。默認值為10%。增加或減少百分比時,請確保將總Java堆調整相同的量。(JVM build > 23)

表格中的 JVM build > 23表示該參數只有在Java HotSpot VM build 大於 23時才能生效,小於或等於都不能生效

6、如果解鎖實驗性參數

       要使用實驗性參數,首先需要解鎖,使用參數-XX:+UnlockExperimentalVMOptions解鎖實驗性參數。

7、建議

       當你在進行G1垃圾收集器調優的時候,請記住以下建議:
       1.年輕代大小:避免使用-Xmn或者其他參數比如-XX:NewRatio設置年輕代大小,固定年輕代的大小將覆蓋暫停目標時間的設置;
       2.暫停時間:在進行垃圾收集器調優時,總是存在延遲時間和吞吐量的權衡,G1收集器是增量式垃圾收集器,具有統一的暫停時間,但在應用線程上的開銷更大。G1收集器的吞吐量目標是90%的應用線程執行時間,10%的垃圾收集時間,與並行收集器相比,並行收集器的吞吐量目標是99%的應用線程時間,1%的垃圾收集時間。因此,當你在對G1的吞吐量進行調優的時候,增大你的暫停時間目標,設置太小的暫停時間,意味着你願意承擔吞吐量降低的風險。當你在對G1進行響應時間調優的時候,你設置你期望的軟實時目標,G1收集器將盡可能的達到該目標,當然,設置響應時間也是又副作用的,吞吐量會響應的受到影響;
       3.混合垃圾收集調優:當在進行混合垃圾收集調優的時候,請使用下面幾個參數進行調試:-XX:InitiatingHeapOccupancyPercent(改變標記閾值)、-XX:G1MixedGCLiveThresholdPercent 和 -XX:G1HeapWastePercent(改變混合收集決策)、-XX:G1MixedGCCountTarget 和 -XX:G1OldCSetRegionThresholdPercent(調整old region集合 CSet)。

8、溢出和耗盡日志。

      當你在G1的GC日志中看到to-space overflow或者to-space exhausted的時候,表示G1沒有足夠的內存使用的(可能是survivor區不夠了,可能是老年代不夠了,也可能是兩者都不夠了),這時候表示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]
       為了解決這個問題,請嘗試做以下調整:
       1.增加預留內存:增大參數-XX:G1ReservePercent的值(相應的增加堆內存)來增加預留內存;
       2.更早的開始標記周期:減小-XX:InitiatingHeapOccupancyPercent參數的值,以更早的開始標記周期;
       3.增加並發收集線程數:增大-XX:ConcGCThreads參數值,以增加並行標記線程數。

9、大對象和大對象分配

       對G1而言,大小超過region大小50%的對象將被認為是大對象,這種大對象將直接被分配到老年代的humongous regions中,humongous regions是連續的region集合, StartsHumongous表記集合從那里開始,ContinuesHumongous標記連續集合。在分配大對象之前,將會檢查標記閾值,如果有必要的話,還會啟動並發周期。死亡的大對象會在標記周期的清理階段和發生Full GC的時候被清理。為了減少復制開銷,任何轉移階段都不包含大對象的復制。在Full GC時,G1在原地壓縮大對象。因為每個獨立的humongous regions只包含一個大對象,因此從大對象的結尾到它占用的最后一個region的結尾的那部分空間時沒有被使用的,對於那些大小略大於region整數倍的對象,這些沒有被使用的內存將導致內存碎片化。如果你看到因為大對象的分配導致不斷的啟動並發收集,並且這種分配使得老年代碎片化不斷加劇,那么請增加-XX:G1HeapRegionSize參數的值,這樣的話,大對象將不再被G1認為是大對象,它會走普通對象的分配流程。

參考資料
1.《深入理解Java虛擬機:JVM高級特性與最佳實踐》(第2版);
2.Java HotSpot Virtual Machine Garbage Collection Tuning Guide Release 8
3.Java垃圾收集必備手冊


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM