JVM垃圾回收之G1收集器

從 我們知道CMS是一種以獲取最短回收停頓時間為目標的收集器,非常適合堆內存大、CPU核數多的服務器端應用。但是CMS並不完美,它有如下缺點:CMS必須要在老年代堆內存用盡之前完成垃圾回收,否則CMS回收失敗時,將觸發擔保機制,

Serial old老年代串行收集器將會以STW的方式進行一次GC,從而造成較大停頓時間;並且標記清理算法無法整理空間碎片,老年代空間會隨著應用時長被逐步耗盡,最後將不得不通過擔保機制對堆內存進行壓縮。

G1(Garbage First)垃圾收集器是當今垃圾回收技術最前沿的成果之一。同CMS一樣,G1也是關注最小時延的垃圾回收器,也同樣適合大尺寸堆內存的垃圾收集。G1收集器是基於標記整理算法,它不會產生內存碎片,沒必要在收集完後,進行一次獨佔式的碎片整理工作。G1最大的特點是引入分區的思路,弱化了分代的概念,合理利用垃圾收集各個週期的資源,解決了其他收集器甚至CMS的眾多缺陷。

JVM垃圾回收之G1收集器

分區

G1採用了分區(Region)的思路,將整個堆空間分成若干個大小相等的內存區域,每次分配對象空間將逐段地使用內存。因此,在堆的使用上,G1並不要求對象的存儲一定是物理上連續的,只要邏輯上連續即可;每個分區也不會確定地為某個代服務,可以按需在年輕代和老年代之間切換。啟動時可以通過參數-XX:G1HeapRegionSize=n指定分區大小(1MB~32MB,且必須是2的冪),默認將整堆劃分為2048個分區。

卡片

在每個分區內部又被分成了若干個大小為512 Byte卡片(Card),標識堆內存最小可用粒度所有分區的卡片將會記錄在全局卡片表(Global Card Table)中,分配的對象會佔用物理上連續的若干個卡片,當查找對分區內對象的引用時便可通過記錄卡片來查找該引用對象(見RSet)。每次對內存的回收,都是對指定分區的卡片進行處理。

G1採用RSet(Remembered Set)

來避免整堆掃描。G1中每個Region都有一個與之對應的Remembered Set,虛擬機發現程序在對Reference類型進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查Reference引用對象是否處於多個Region中(即檢查老年代中是否引用了新生代中的對象),如果是,就通過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set中。當進行內存回收時,在GC根節點的枚舉範圍中加入Remembered Set即可保證不對全堆進行掃描也不會有遺漏。

堆 Heap

G1同樣可以通過-Xms/-Xmx來指定堆空間大小。當發生年輕代收集(Young GC)或混合收集(Mixed GC)時,通過計算GC與應用的耗費時間比,自動調整堆空間大小。如果GC頻率太高,則通過增加堆尺寸,來減少GC頻率,相應地GC佔用的時間也隨之降低;目標參數-XX:GCTimeRatio即為GC與應用的耗費時間比,G1默認為9,而CMS默認為99,因為CMS的設計原則是耗費在GC上的時間儘可能的少。另外,當空間不足,如對象空間分配或轉移失敗時,G1會首先嚐試增加堆空間,如果擴容失敗,則發起擔保的Full GC。Full GC後,堆尺寸計算結果也會調整堆空間。

Young GC年輕代收集

在分配一般對象(非巨型對象)時,當所有Eden region使用達到最大閥值並且無法申請足夠內存時,會觸發一次Young GC。每次Young GC會回收所有Eden以及Survivor區,並且將存活對象複製到Old區以及另一部分的Survivor區。會複製到Old區的標準就是在PLAB中得到的計算結果。因為Young GC會進行根掃描,所以會STW。

MixedGC混合收集

MixedGC是G1 GC特有的,跟Full GC不同的是Mixed GC只回收部分老年代的Region。MixedGC一般會發生在一次YoungGC後面,為了提高效率,MixedGC會複用YoungGC的全局的根掃描結果,因為這個STW過程是必須的,整體上來說縮短了暫停時間。

MixedGC的回收過程可以理解為Young GC後附加的全局concurrent marking,全局的併發標記主要用來處理old區(包含H區)的存活對象標記,過程如下:

JVM垃圾回收之G1收集器

如果不計算維護 Remembered Set 的操作,G1收集器大致可分為如下步驟:

初始標記:僅標記GC Roots能直接到的對象,並且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序併發運行時,能在正確可用的Region中創建新對象。(需要線程停頓,但耗時很短。)

併發標記:從GC Roots開始對堆中對象進行可達性分析,找出存活對象。(耗時較長,但可與用戶程序併發執行)

最終標記:為了修正在併發標記期間因用戶程序執行而導致標記產生變化的那一部分標記記錄。且對象的變化記錄在線程Remembered Set Logs裡面,把Remembered Set Logs裡面的數據合併到Remembered Set中。(需要線程停頓,但可並行執行。)

篩選回收:對各個Region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間來制定回收計劃。(可併發執行)

Full GC

G1在對象複製/轉移失敗或者沒法分配足夠內存(比如巨型對象沒有足夠的連續分區分配)時,會觸發Full GC。Full GC使用的是STW的Serial Old模式,所以一旦觸發Full GC則會STW應用線程,並且執行效率很慢。JDK 8版本的G1是不提供Full GC的處理的。對於G1 GC的優化,很大的目標就是沒有Full GC。

參數配置:

  • -XX:+UseG1GC 使用 G1 垃圾收集器。
  • -XX:MaxGCPauseMillis=200 設置期望達到的最大GC停頓時間指標(JVM會盡力實現,但不保證達到)。
  • -XX:InitiatingHeapOccupancyPercent=n 啟動併發GC週期時的堆內存佔用百分比。 G1之類的垃圾收集器用它來觸發併發GC週期,基於整個堆的使用率,而不只是某一代內存的使用比。值為 0 則表示”一直執行GC循環”。 默認值為 45。
  • -XX:NewRatio=n 新生代與老生代(new/old generation)的大小比例(Ratio)。 默認值為 2。
  • -XX:SurvivorRatio=n eden/survivor 空間大小的比例(Ratio)。默認值為 8。
  • -XX:MaxTenuringThreshold=n 提升年老代的最大臨界值(tenuring threshold)。 默認值為 15。
  • -XX:ParallelGCThreads=n 設置垃圾收集器在並行階段使用的線程數,默認值隨JVM運行的平臺不同而不同。
  • -XX:ConcGCThreads=n 併發垃圾收集器使用的線程數量。默認值隨JVM運行的平臺不同而不同。
  • -XX:G1ReservePercent=n 設置堆內存保留為假天花板的總量,以降低提升失敗的可能性。 默認值是 10。
  • -XX:G1HeapRegionSize=n 使用G1時Java堆會被分為大小統一的的區(region)。此參數可以指定每個heap區的大小.。默認值將根據 heap size 算出最優解. 最小值為 1Mb,最大值為 32Mb。

總結

G1是一款非常優秀的垃圾收集器,不僅適合堆內存大的應用,同時也簡化了調優的工作。通過主要的參數初始和最大堆空間、以及最大容忍的GC暫停目標,就能得到不錯的性能;同時,我們也看到G1對內存空間的浪費較高,但通過先收集儘可能多的垃圾(G1)的設計原則,可以及時發現過期對象,從而讓內存佔用處於合理的水平。


分享到:


相關文章: