JVM中G1垃圾回收器詳細解析

stackvoid發表於2015-03-16

我們先回顧一下主流Java的垃圾回收器(HotSpot JVM)。本文是針對堆的垃圾回收展開討論的。

堆被分解為較小的三個部分。具體分為:新生代、老年代、持久代。

JVM1

  1. 絕大部分新生成的物件都放在Eden區,當Eden區將滿,JVM會因申請不到記憶體,而觸發Young GC ,進行Eden區+有物件的Survivor區(設為S0區)垃圾回收,把存活的物件用複製演算法拷貝到一個空的Survivor(S1)中,此時Eden區被清空,另外一個Survivor S0也為空。下次觸發Young GC回收Eden+S0,將存活物件拷貝到S1中。新生代垃圾回收簡單、粗暴、高效。
  2. 若發現Survivor區滿了,則將這些物件拷貝到old區或者Survivor沒滿但某些物件足夠Old,也拷貝到Old區(每次Young GC都會使Survivor區存活物件值+1,直到閾值)。 3.Old區也會進行垃圾收集(Young GC),發生一次 Major GC 至少伴隨一次Young GC,一般比Young GC慢十倍以上。
  3. JVM在Old區申請不到記憶體,會進行Full GC。Old區使用一般採用Concurrent-Mark–Sweep策略回收記憶體。

總結:Java垃圾回收器是一種“自適應的、分代的、停止—複製、標記-清掃”式的垃圾回收器。

缺點:

  1. GC過程中會出現STW(Stop-The-World),若Old區物件太多,STW耗費大量時間。
  2. CMS收集器對CPU資源很敏感。
  3. CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。
  4. CMS導致記憶體碎片問題。

G1收集器

在G1中,堆被劃分成 許多個連續的區域(region)。每個區域大小相等,在1M~32M之間。JVM最多支援2000個區域,可推算G1能支援的最大記憶體為2000*32M=62.5G。區域(region)的大小在JVM初始化的時候決定,也可以用-XX:G1HeapReginSize設定。

在G1中沒有物理上的Yong(Eden/Survivor)/Old Generation,它們是邏輯的,使用一些非連續的區域(Region)組成的。

新生代收集

G1的新生代收集跟ParNew類似,當新生代佔用達到一定比例的時候,開始出發收集。

JVM2

JVM3

被圈起的綠色部分為新生代的區域(region),經過Young GC後存活的物件被複制到一個或者多個區域空閒中,這些被填充的區域將是新的新生代;當新生代物件的年齡(逃逸過一次Young GC年齡增加1)已經達到某個閾值(ParNew預設15),被複制到老年代的區域中。

回收過程是停頓的(STW,Stop-The-Word);回收完成之後根據Young GC的統計資訊調整Eden和Survivor的大小,有助於合理利用記憶體,提高回收效率。

回收的過程多個回收執行緒併發收集。

老年代收集

和CMS類似,G1收集器收集老年代物件會有短暫停頓。

  1. 標記階段,首先初始標記(Initial-Mark),這個階段是停頓的(Stop the World Event),並且會觸發一次普通Mintor GC。對應GC log:GC pause (young) (inital-mark)
  2. Root Region Scanning,程式執行過程中會回收survivor區(存活到老年代),這一過程必須在young GC之前完成。
  3. Concurrent Marking,在整個堆中進行併發標記(和應用程式併發執行),此過程可能被young GC中斷。在併發標記階段,若發現區域物件中的所有物件都是垃圾,那個這個區域會被立即回收(圖中打X)。同時,併發標記過程中,會計算每個區域的物件活性(區域中存活物件的比例)。JVM4
  4. Remark, 再標記,會有短暫停頓(STW)。再標記階段是用來收集 併發標記階段 產生新的垃圾(併發階段和應用程式一同執行);G1中採用了比CMS更快的初始快照演算法:snapshot-at-the-beginning (SATB)。
  5. Copy/Clean up,多執行緒清除失活物件,會有STW。G1將回收區域的存活物件拷貝到新區域,清除Remember Sets,併發清空回收區域並把它返回到空閒區域連結串列中。JVM5
  6. 複製/清除過程後。回收區域的活性物件已經被集中回收到深藍色和深綠色區域。

JVM6

關於Remembered Set概念:G1收集器中,Region之間的物件引用以及其他收集器中的新生代和老年代之間的物件引用是使用Remembered Set來避免掃描全堆。G1中每個Region都有一個與之對應的Remembered Set,虛擬機器發現程式對Reference型別資料進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查Reference引用的物件是否處於不同的Region之間(在分代中例子中就是檢查是否老年代中的物件引用了新生代的物件),如果是便通過CardTable把相關引用資訊記錄到被引用物件所屬的Region的Remembered Set中。當記憶體回收時,在GC根節點的列舉範圍加入Remembered Set即可保證不對全域性堆掃描也不會有遺漏。

G1雖然保留了CMS關於代的概念,但是代已經不是物理上連續區域,而是一個邏輯的概念。在標記過程中,每個區域的物件活性都被計算,在回收時候,就可以根據使用者設定的停頓時間,選擇活性較低的區域收集,這樣既能保證垃圾回收,又能保證停頓時間,而且也不會降低太多的吞吐量。Remark階段新演算法的運用,以及收集過程中的壓縮,都彌補了CMS不足。引用Oracle官網的一句話:“G1 is planned as the long term replacement for the Concurrent Mark-Sweep Collector (CMS)”。

相關文章