JVM垃圾收集器與內存分配策略(四)—— Garbage First

Garbage First 收集器

Garbage First(簡稱G1)收集器時垃圾收集器技術發展史上的里程碑式的成果,它開創了收集器面向局部收集的設計思路和基於Region的內存佈局形式。G1是一款主要面向服務端應用的垃圾收集器。

HotSpot開發團隊最初賦予它的期望是(在比較長期的)未來可以替換掉JDK5中發佈的CMS收集器。
作爲CMS收集器的替代者和繼承人,設計者們希望做出一款能夠建立起“停頓時間模型”(Pause Prediction Model)的收集器,停頓時間模型的意思是能夠支持指定在一個長度爲M毫秒的時間片段內,消耗在垃圾收集上的時間大概率不超過N毫秒這樣的目標,這機會已經是實時Java(RTSJ)中的軟實時垃圾收集器的特徵了。

爲了實現這個目標,首先得要有一個思想上的轉變,在G1收集器出現之前的其他所有收集器,包括CMS在內,垃圾收集的目標範圍要麼是整個新生代(Minor GC),要麼是整個老年代(Major GC),再要麼就是整個Java堆(Full GC)。而G1跳出了這個樊籠,它可以面向對內任何部分來組成回收集(Collecion Set,一般簡稱CSet)進行回收,衡量的標準不再是它屬於哪個分代,而是哪塊內存中存放的垃圾數量多,回收收益最大,這就是G1收集器的Mixed GC模式。
G1開創的基於Region的堆內存佈局是它能夠實現這個目標的關鍵。雖然G1仍然是遵循分代收集理論設計的,但其堆內存的佈局與其他收集器有非常明顯的差異:G1不再堅持固定大小以及固定數量的分代區域劃分,而是把連續的Java堆劃分爲多個大小相等的獨立區域(Region),每一個Region都可以根據需要,扮演新生代的Eden空間、Survivor空間,或者老年代空間。收集器能夠堆扮演不同角色的Region採用不同的策略去處理,這樣無論是新創建的而對象還是已經存活了一段時間、熬過多次收集的舊對象都能獲取良好的收集效果。

Region中還有一類特殊的Humongous區域,專門用來存儲大對象。G1認爲只要大小超過了一個Region容量一半的對象即可判定爲大對象。每個Region的大小可以通過參數-XX:G1HeapRegionSize設定,取值範圍爲1M~32M,且應該爲2的N次冪。而對於那些超過了整個Region容量的超級大對象,將會被存放在N個連續的Humongous Region之中,G1的大多數行爲都把Humongous Region當作老年代的一部分進行看待。

雖然G1仍然保留新生到和老年代的概念,但新生代和老年代不再是固定的了,它們都是一系列區域(不需要連續)的動態集合。G1收集器之所以能建立可預測時間停頓模型,是因爲它將Region作爲單次回收的最小單元,即每次回收的空間都是Region大小的整數倍,這樣可以有計劃地避免在整個Java堆進行全區域的垃圾收集。更具體的處理思路是讓G1收集器區跟蹤各個Region裏面的垃圾堆積的“價值”大小,價值即回收所獲得的空間大小以及回收所需要時間的經驗值,然後再後臺維護一個優先級列表,每次很具用戶設定允許的收集停頓時間(使用參數-XX:MaxGCPauseMillis指定,默認值是200毫秒),優先處理回收價值收益最大的那些Region,這也就是“Garbage First”名字的由來。這種使用Region劃分內存空間,以及具有優先級的區域回收方式,保證了G1收集器在有限的時間內獲取儘可能高的收集效率。
忽略用戶線程運行過程中的動作(如使用寫屏障維護記憶表的操作),G1收集器運作過程大致可劃分爲以下四個步驟:

  • 初始標記(Initial Marking):僅僅只是標記一下GC Roots能直接關聯到的對象,並且修改TAMS指針的值,讓下一階段用戶線程併發運行時,能正確的在可用的Region中分配新對象。這個階段需要線程停頓,但耗時很短,而且時借用進行Minor GC 的時候同步完成的,所以G1收集器在這個階段實際並沒有額外的停頓。
  • 併發標記(Concurrent Marking):從GC Roots開始對堆中的對象進行可達性分析,遞歸掃描整個堆裏的對象圖,找出要回收的對象,這階段耗時較長,但可與用戶程序併發執行。當對象圖掃描完成以後,還有重新處理SATB記錄下的在併發時有引用變動的對象。
  • 最終標記(Final Marking):對用戶線程做另一個短暫的暫停,用於處理併發階段結束後仍遺留下來的最後那少量的SATB記錄。
  • 篩選回收(Live Data Counting and Evacuation):複製更新Region的統計數據,對各個Region的回收價值和成本進行排序,根據用戶所期望的停頓時間來制定回收計劃,可以自由選中任意多個Region構成回收集,然後把決定回收的那一部分Region的存活對象複製到空的Region中,再清理掉整個舊的Region的全部空間。這裏的操作涉及存活對象的移動,是必須暫停掉用戶線程,由多條收集器線程並行完成的。

從上述階段的描述可以看出,G1收集器除了併發標記外,其餘階段也是要完全暫停用戶線程的,換言之,它並非純粹地追求低延遲,官方給它設定的目標是在延遲可控的情況下獲得儘可能高的吞吐量,所以才能擔當起“全功能收集器”的重任與期望。1

G1收集器運行示意圖:
G1收集器運行示意圖
從G1開始,最先進的垃圾收集器的涉及到想都不約而同地變爲追求能夠應付應用的內存分配速率(Allocation Rate),而不追求一次把整個Java堆全部清理乾淨。這樣,應用再分配,同時收集器在收集,只要收集的速度跟得上對象分配的速度,那一切運作的很完美。這種新的收集器設計思路從工程實現上看是從G1開始興起的,所以說G1是收集器技術發展的一個里程碑。


  1. 原文是:It meets garbage collection pause time goals with a high probability,while achieving high throughput。 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章