深刻理解G1垃圾收集器

深刻理解G1垃圾收集器算法

G1 GC是Jdk7的新特性之1、Jdk7+版本均可以自主配置G1做爲JVM GC選項;做爲JVM GC算法的一次重大升級、DK7u後G1已相對穩定、且將來計劃替代CMS、因此有必要深刻了解下:編程

不一樣於其餘的分代回收算法、G1將堆空間劃分紅了互相獨立的區塊。每塊區域既有可能屬於O區、也有多是Y區,且每類區域空間能夠是不連續的(對比CMS的O區和Y區都必須是連續的)。這種將O區劃分紅多塊的理念源於:當併發後臺線程尋找可回收的對象時、有些區塊包含可回收的對象要比其餘區塊多不少。雖然在清理這些區塊時G1仍然須要暫停應用線程、但能夠用相對較少的時間優先回收包含垃圾最多區塊。這也是爲何G1命名爲Garbage First的緣由:第一時間處理垃圾最多的區塊。併發

 

平時工做中大多數系統都使用CMS、即便靜默升級到JDK7默認仍然採用CMS、那麼G1相對於CMS的區別在:高併發

  1. G1在壓縮空間方面有優點
  2. G1經過將內存空間分紅區域(Region)的方式避免內存碎片問題
  3. Eden, Survivor, Old區再也不固定、在內存使用效率上來講更靈活
  4. G1能夠經過設置預期停頓時間(Pause Time)來控制垃圾收集時間避免應用雪崩現象
  5. G1在回收內存後會立刻同時作合併空閒內存的工做、而CMS默認是在STW(stop the world)的時候作
  6. G1會在Young GC中使用、而CMS只能在O區使用

就目前而言、CMS仍是默認首選的GC策略、可能在如下場景下G1更適合:spa

  1. 服務端多核CPU、JVM內存佔用較大的應用(至少大於4G)
  2. 應用在運行過程當中會產生大量內存碎片、須要常常壓縮空間
  3. 想要更可控、可預期的GC停頓週期;防止高併發下應用雪崩現象

一次完整G1GC的詳細過程:

G1在運行過程當中主要包含以下4種操做方式:線程

  1. YGC(不一樣於CMS)
  2. 併發階段
  3. 混合模式
  4. full GC (通常是G1出現問題時發生)

YGC:

下面是一次YGC先後內存區域是示意圖:日誌

圖中每一個小區塊都表明G1的一個區域(Region),區塊裏面的字母表明不一樣的分代內存空間類型(如[E]Eden,[O]Old,[S]Survivor)空白的區塊不屬於任何一個分區;G1能夠在須要的時候任意指定這個區域屬於Eden或是O區之類的。
G1 YoungGC在Eden充滿時觸發,在回收以後全部以前屬於Eden的區塊全變成空白。而後至少有一個區塊是屬於S區的(如圖半滿的那個區域),同時可能有一些數據移到了O區。code

目前淘系的應用大都使用PrintGCDetails參數打出GC日誌、這個參數對G1一樣有效、但日誌內容頗爲不一樣;下面是一個Young GC的例子:對象

23.430: [GC pause (young), 0.23094400 secs]
...
[Eden: 1286M(1286M)->0B(1212M)
Survivors: 78M->152M Heap: 1454M(4096M)->242M(4096M)]
[Times: user=0.85 sys=0.05, real=0.23 secs]內存

上面日誌的內容解析:Young GC實際佔用230毫秒、其中GC線程佔用850毫秒的CPU時間
E:內存佔用從1286MB變成0、都被移出
S:從78M增加到了152M、說明從Eden移過來74M
Heap:佔用從1454變成242M、說明此次Young GC一共釋放了1212M內存空間
不少狀況下,S區的對象會有部分晉升到Old區,另外若是S區已滿、Eden存活的對象會直接晉升到Old區,這種狀況下Old的空間就會漲

併發階段:

一個併發G1回收週期先後內存佔用狀況以下圖所示:

從上面的圖表能夠看出如下幾點:
一、Young區發生了變化、這意味着在G1併發階段內至少發生了一次YGC(這點和CMS就有區別),Eden在標記以前已經被徹底清空,由於在併發階段應用線程同時在工做、因此能夠看到Eden又有新的佔用
二、一些區域被X標記,這些區域屬於O區,此時仍然有數據存放、不一樣之處在G1已標記出這些區域包含的垃圾最多、也就是回收收益最高的區域
三、在併發階段完成以後實際上O區的容量變得更大了(O+X的方塊)。這時由於這個過程當中發生了YGC有新的對象進入所致。此外,這個階段在O區沒有回收任何對象:它的做用主要是標記出垃圾最多的區塊出來。對象其實是在後面的階段真正開始被回收

G1併發標記週期能夠分紅幾個階段、其中有些須要暫停應用線程。第一個階段是初始標記階段。這個階段會暫停全部應用線程-部分緣由是這個過程會執行一次YGC、下面是一個日誌示例:

50.541: [GC pause (young) (initial-mark), 0.27767100 secs]
[Eden: 1220M(1220M)->0B(1220M)
Survivors: 144M->144M Heap: 3242M(4096M)->2093M(4096M)]
[Times: user=1.02 sys=0.04, real=0.28 secs]

上面的日誌代表發生了YGC、應用線程爲此暫停了280毫秒,Eden區被清空(71MB從Young區移到了O區)。
日誌裏面initial-mark的字樣代表後臺的併發GC階段開始了。由於初始標記階段自己也是要暫停應用線程的,
G1正好在YGC的過程當中把這個事情也一塊兒幹了。爲此帶來的額外開銷不是很大、增長了20%的CPU,暫停時間相應的略微變長了些。

接下來,G1開始掃描根區域、日誌示例:

50.819: [GC concurrent-root-region-scan-start]
51.408: [GC concurrent-root-region-scan-end, 0.5890230]

一共花了580毫秒,這個過程沒有暫停應用線程;是後臺線程並行處理的。這個階段不能被YGC所打斷、所以後臺線程有足夠的CPU時間很關鍵。若是Young區空間剛好在Root掃描的時候
滿了、YGC必須等待root掃描以後才能進行。帶來的影響是YGC暫停時間會相應的增長。這時的GC日誌是這樣的:

350.994: [GC pause (young)
351.093: [GC concurrent-root-region-scan-end, 0.6100090]
351.093: [GC concurrent-mark-start],0.37559600 secs]

GC暫停這裏能夠看出在root掃描結束以前就發生了,代表YGC發生了等待,等待時間大概是100毫秒。
在root掃描完成後,G1進入了一個併發標記階段。這個階段也是徹底後臺進行的;GC日誌裏面下面的信息表明這個階段的開始和結束:

111.382: [GC concurrent-mark-start]
....
120.905: [GC concurrent-mark-end, 9.5225160 sec]

併發標記階段是能夠被打斷的,好比這個過程當中發生了YGC就會。這個階段以後會有一個二次標記階段和清理階段:

120.910: [GC remark 120.959:
[GC ref-PRC, 0.0000890 secs], 0.0718990 secs]
[Times: user=0.23 sys=0.01, real=0.08 secs]
120.985: [GC cleanup 3510M->3434M(4096M), 0.0111040 secs]
[Times: user=0.04 sys=0.00, real=0.01 secs]

這兩個階段一樣會暫停應用線程,但時間很短。接下來還有額外的一次併發清理階段:

120.996: [GC concurrent-cleanup-start]
120.996: [GC concurrent-cleanup-end, 0.0004520]

到此爲止,正常的一個G1週期已完成–這個週期主要作的是發現哪些區域包含可回收的垃圾最多(標記爲X),實際空間釋放較少。

混合GC:

接下來G1執行一系列的混合GC。這個時期由於會同時進行YGC和清理上面已標記爲X的區域,因此稱之爲混合階段,下面是一個混合GC執行的先後示意圖:

像普通的YGC那樣、G1徹底清空掉Eden同時調整survivor區。另外,兩個標記也被回收了,他們有個共同的特色是包含最多可回收的對象,所以這兩個區域絕對部分空間都被釋放了。這兩個區域任何存活的對象都被移到了其餘區域(和YGC存活對象晉升到O區相似)。這就是爲何G1的堆比CMS內存碎片要少不少的緣由–移動這些對象的同時也就是在壓縮對內存。下面是一個混合GC的日誌:

79.826: [GC pause (mixed), 0.26161600 secs]
....
[Eden: 1222M(1222M)->0B(1220M)
Survivors: 142M->144M Heap: 3200M(4096M)->1964M(4096M)]
[Times: user=1.01 sys=0.00, real=0.26 secs]

上面的日誌能夠注意到Eden釋放了1222MB、但整個堆的空間釋放內存要大於這個數目。數量相差看起來比較少、只有16MB,可是要考慮同時有survivor區的對象晉升到O區;另外,每次混合GC只是清理一部分的O區內存,整個GC會一直持續到幾乎全部的標記區域垃圾對象都被回收,這個階段完了以後G1會從新回到正常的YGC階段。週期性的,當O區內存佔用達到必定數量以後G1又會開啓一次新的並行GC階段.

原創文章,轉載請註明: 轉載自併發編程網 – ifeve.com本文連接地址: 深刻理解G1垃圾收集器

相關文章
相關標籤/搜索