1. 程式人生 > >G1垃圾收集器

G1垃圾收集器

概述

  G1垃圾收集器(Garbage First)是一個並行的、併發的、面向伺服器的垃圾收集器的垃圾收集器。G1在Oracle JDK 7 update 4 及以上版本中得到完全支援,它的長遠目標時代替CMS收集器。相較於CMS,G1是一款壓縮型的收集器,不會產生記憶體碎片;可以極高概率滿足GC停頓時間,實現低停頓垃圾回收。

  G1是區域化、分代式垃圾回收器, Java物件堆被劃分成若干個大小相同的區域(Region)。啟動時,JVM初始化的時候決定region的大小,可以用-XX:G1HeapReginSize指定,Region的大小一定是1 MB到32 MB之間數,並且是2的冪,例如4M、16M。所有的Region都是指定的大小,在JVM生命週期內不會改變。G1的目標是產生不超過2048 個同樣大小的區域,但是如果設定的Java堆大於64G,JVM會適當增加region的數量,但是region大小一定不會超過32M。G1會並且跟蹤這些區域的垃圾收集進度,同時在後臺維護一個優先順序列表,每次從可回收空間最多的區域開始,儘可能回收更多的堆空間,同時儘可能不超出暫停時間目。

  在G1中沒有物理上的Yong Generation(Eden/Survivor)、Old Generation,它們是邏輯的,使用一些非連續的區域(Region)組成的。而且不需要在JVM啟動時決定哪些Region屬於老年代,哪些屬於年輕代,一個Region可能這次GC是young region,而下次GC卻是old region。而且G1 GC 有一個力求達到的暫停時間目標(軟實時),在年輕代回收期間,G1 GC還會調整其年輕代空間(young region個數)以滿足軟實時目標。

Region型別

  • Available region:可用的空閒區域。
  • Eden region
    : 新生代的eden區。
  • Survivor region:新生代的survivor區。
  • Humongous region: 大區。
      注意:eden region和survivor region的數量並不固定,可能伴隨著GC而發生變化(young, mixed, or full GCs)。
      對G1來說,任何超過區域一半大小的物件都被視為“巨型物件(Humongous Object)”。當需要分配這種物件時,G1會找出總計記憶體足夠包含該物件的一組連續的可用區域來分配該物件,第一個region會標記為StartsHumongous ,其它延續的region被標記為ContinuesHumongous。在分配任何巨型區域之前,會檢查標記閾值,如有必要,還會啟動一個併發週期。如果沒有這麼大的連續的可用區域,G1會作一次FGC來壓縮Java堆。儘管只有一個物件,Humongous regions被認為是老年代的一部分。這樣設計的目的是為了使G1在併發標記階段,如果發現物件不可用時儘早的回收掉這些region。

CSet與RSet

  CSet即Collection Set,它是需要回收的region集合,在年輕代垃圾收集中CSet僅包含young region,在混合垃圾收集中CSet不僅包含young region,還有一些old region。

  G1中每個Region都有一個與之對應的Remembered Set,每一個RSet是一個數據結構,用以維護和跟蹤Region之間的物件引用。每個region有獨立的Remembered Set(RSet),減少了全堆掃描獲取資訊的耗時。
  虛擬機發現程式在對Reference型別的資料進行寫操作時,會檢查Reference引用的物件是否處於不同的Region之中(在分代的例子中就是檢查引是否老年代中的物件引用了新生代中的物件),如果是,便把相關引用資訊記錄到被引用物件所屬的Region的Remembered Set之中。當進行記憶體回收時,GC根節點的列舉範圍中加入Remembered Set即可保證不對全堆掃描也不會有遺漏。

GC方式

G1在執行過程中主要包含如下4種GC方式

  • 年輕代垃圾收集(young collection cycle)
      當應用程式開始分配物件時,G1會在eden region分配新物件,直到eden region不夠分配新物件時,young GC開始垃圾回收。在年輕代垃圾回收期間,G1 GC會同時回收eden區域和survivor區域。此階段會有一次stop the world(STW)暫停。
      垃圾收集過程,G1將所有存活的物件從Eden Region移動到Survivor Region,即“copy to survivor”;然後晉升年輕代的存活物件到新的survivor區,對於那些年齡達到閾值(tenuring threshold)的物件會晉升到老年代。物件的晉升過程發生在負責晉升的GC執行緒(promoting GC thread)的本地分配緩衝區(promotion local allocation buffer,PLAB),每個GC-thread都有針對與survivor區或old區的PLAB。
      每次YGC暫停階段G1會依據此次垃圾收集時間總時間、RSet大小、年輕代大小、暫停目標等指標計算此次年輕代垃圾收集代價,在暫停階段結束後會基於此來重新調整年輕代的大小。

  • 併發標記(concurrent marking cycle)
      一次GC之後,當老年代的佔用空間超過設定的閾值、metaspace空間超過閾值時,G1開始執行老年代的垃圾回收。通過-XX:InitiatingHeapOccupancyPercent來設定閾值,預設是45,即佔用空間達到堆空間(the entire Java heap)的45%時開始併發標記階段。
      併發標記包括以下幾個階段:

    • initial marking(初始標記階段):在此階段對所有的GC root進行標記,會觸發一次young GC,需要stop-the-world。對應GC日誌中的GC pause (young) (inital-mark)。

    • concurrent root region scanning(根區域掃描階段):掃描和跟蹤survivor區中的物件的所有的引用。該階段是併發的,GC執行緒和應用執行緒一起執行。只有完成該階段,才會開始下一次年輕代GC。因為下一次年輕代GC時產生的新的survivor物件,是不同於initial marking階段後的survivor物件。

    • concurrent marking(併發標記階段):在整個堆中查詢可訪問的(存活的)物件。該階段與應用程式同時執行,此過程可能被young GC中斷。在併發標記階段,若發現區域物件中的所有物件都是垃圾會被立即回收。同時,併發標記過程中,會計算每個區域的物件活性(區域中存活物件的比例)。-XX:ConcGCThreads可以設定該過程的並行度,預設是ParallelGCThreads數量的四分之一。

    • remarking(重新標記階段):該階段會有停頓(STW),幫助完成標記週期,用來標記併發標記階段產生新的垃圾。G1 GC清空 SATB日誌緩衝區,跟蹤未被訪問的存活物件,並執行引用處理。該階段是並行的,-XX:ParallelGCThreads指定並行數。

    • cleanup(清理階段):在這個最後階段,G1 GC 執行統計和RSet重置,會有短暫STW。在統計期間,G1 GC會識別完全空閒的區域和可供進行混合垃圾回收的區域。如果有不包含存活物件的region(即完全空閒的區域),將會有額外的concurrent-cleanup階段,該階段將空白區域重置並返回到空閒列表。G1會依據老年代的“GC efficiency”作排序。

  注意:如果應用程式的存活物件圖非常大,那麼concurrent marking cycle所耗用的時間也就越多,而且concurrent marking cycle階段被young collection打斷的次數也會變得頻繁。
  marking threshold的設定非常重要,過大會導致增大發生evacuation failures的風險,過小會導致過早的觸發併發標記階段,而且可能沒有垃圾需要回收。如果marking threshold的設定恰當,但併發週期的時間過長,導致混合GC階段回收速率跟不上分配速率而觸發evacuation failures,那麼可以適當的增加併發執行緒。-XX:ConcGCThreads預設是-XX:ParallelGCThreads的四分之一,可以直接增大併發執行緒數量或則增大並行數。但是,需要考慮的是,增加併發執行緒會影響應用程式的執行緒,因為總的硬體資源一定。

  • 混合垃圾收集(mixed collection cycle)
      併發標記週期完成後將緊接著發生一次young collection,young collection的目的是決定是否需要觸發mixed collection,如果可回收的region容量大於-XX:G1HeapWastePercent,則開始Mixed GC。
      在混合垃圾收集期間,G1 GC不僅將eden和survivor區新增到CSet,還包括併發標記階段標記出的old區的一部分新增到CSet來作垃圾回收。所新增old區域的確切數量由一系列標誌控制。G1 GC 回收了足夠的old區域後(經過多次混合垃圾回收),G1 將恢復執行年輕代垃圾回收,直到下一個併發標記週期完成。

  • Full GC
      Full GC採用的類似於Serial GC一樣的收集演算法,當Full GC發生時,整個Java堆將會做一次壓縮,以確保足夠多的記憶體可用。但是G1中的FGC是單執行緒的,也會導致很長的停頓時間。當 to-space exhausted/overflow 發生時G1將採取Full GC,當然可以通過適當的引數調優,可以不觸發FGC也能滿足應用的效能目標。

Evacuation Failure

  對 survivors/promoted objects 進行GC時,如果JVM的heap區不足就會發生提升失敗(promotion failure),如果Java堆又不能再繼續擴充套件時將導致evacuation failure,此後就會進行一次FGC。
  當使用 -XX:+PrintGCDetails 出現evacuation failure將會在GC日誌中顯示:

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]

  如何避免evacuation failure,請嘗試以下調整:

  • 增加 -XX:G1ReservePercent 選項的值(並相應增加總的堆大小),為“目標空間”增加預留記憶體量,在需要更大’to-space’的情況下會嘗試從該預存記憶體獲取。
  • 通過減少 -XX:InitiatingHeapOccupancyPercent提前啟動標記週期。
  • 增加-XX:ConcGCThreads選項的值來增加併發標記時的並行標記執行緒數目,以儘快的完成標記從而進入混合GC。

參考:http://www.oracle.com/technetwork/cn/articles/java/g1gc-1984535-zhs.html
   http://blog.csdn.net/renfufei/article/details/41897113
   http://ifeve.com/深入理解g1垃圾收集器/
   http://blog.csdn.net/lujinhong2/article/details/51130910