[转]深入理解G1垃圾收集器
G1 GC是Jdk7的新特性之一、Jdk7+版本都可以自主配置G1作為JVM GC選項(xiàng);作為JVM GC算法的一次重大升級(jí)、DK7u后G1已相對(duì)穩(wěn)定、且未來(lái)計(jì)劃替代CMS、所以有必要深入了解下:
不同于其他的分代回收算法、G1將堆空間劃分成了互相獨(dú)立的區(qū)塊。每塊區(qū)域既有可能屬于O區(qū)、也有可能是Y區(qū),且每類區(qū)域空間可以是不連續(xù)的(對(duì)比CMS的O區(qū)和Y區(qū)都必須是連續(xù)的)。這種將O區(qū)劃分成多塊的理念源于:當(dāng)并發(fā)后臺(tái)線程尋找可回收的對(duì)象時(shí)、有些區(qū)塊包含可回收的對(duì)象要比其他區(qū)塊多很多。雖然在清理這些區(qū)塊時(shí)G1仍然需要暫停應(yīng)用線程、但可以用相對(duì)較少的時(shí)間優(yōu)先回收包含垃圾最多區(qū)塊。這也是為什么G1命名為Garbage First的原因:第一時(shí)間處理垃圾最多的區(qū)塊。
?
平時(shí)工作中大多數(shù)系統(tǒng)都使用CMS、即使靜默升級(jí)到JDK7默認(rèn)仍然采用CMS、那么G1相對(duì)于CMS的區(qū)別在:
就目前而言、CMS還是默認(rèn)首選的GC策略、可能在以下場(chǎng)景下G1更適合:
一次完整G1GC的詳細(xì)過(guò)程:
G1在運(yùn)行過(guò)程中主要包含如下4種操作方式:
YGC:
下面是一次YGC前后內(nèi)存區(qū)域是示意圖:
圖中每個(gè)小區(qū)塊都代表G1的一個(gè)區(qū)域(Region),區(qū)塊里面的字母代表不同的分代內(nèi)存空間類型(如[E]Eden,[O]Old,[S]Survivor)空白的區(qū)塊不屬于任何一個(gè)分區(qū);G1可以在需要的時(shí)候任意指定這個(gè)區(qū)域?qū)儆贓den或是O區(qū)之類的。
G1 YoungGC在Eden充滿時(shí)觸發(fā),在回收之后所有之前屬于Eden的區(qū)塊全變成空白。然后至少有一個(gè)區(qū)塊是屬于S區(qū)的(如圖半滿的那個(gè)區(qū)域),同時(shí)可能有一些數(shù)據(jù)移到了O區(qū)。
目前淘系的應(yīng)用大都使用PrintGCDetails參數(shù)打出GC日志、這個(gè)參數(shù)對(duì)G1同樣有效、但日志內(nèi)容頗為不同;下面是一個(gè)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]
上面日志的內(nèi)容解析:Young GC實(shí)際占用230毫秒、其中GC線程占用850毫秒的CPU時(shí)間
E:內(nèi)存占用從1286MB變成0、都被移出
S:從78M增長(zhǎng)到了152M、說(shuō)明從Eden移過(guò)來(lái)74M
Heap:占用從1454變成242M、說(shuō)明這次Young GC一共釋放了1212M內(nèi)存空間
很多情況下,S區(qū)的對(duì)象會(huì)有部分晉升到Old區(qū),另外如果S區(qū)已滿、Eden存活的對(duì)象會(huì)直接晉升到Old區(qū),這種情況下Old的空間就會(huì)漲
并發(fā)階段:
一個(gè)并發(fā)G1回收周期前后內(nèi)存占用情況如下圖所示:
從上面的圖表可以看出以下幾點(diǎn):
1、Young區(qū)發(fā)生了變化、這意味著在G1并發(fā)階段內(nèi)至少發(fā)生了一次YGC(這點(diǎn)和CMS就有區(qū)別),Eden在標(biāo)記之前已經(jīng)被完全清空,因?yàn)樵诓l(fā)階段應(yīng)用線程同時(shí)在工作、所以可以看到Eden又有新的占用
2、一些區(qū)域被X標(biāo)記,這些區(qū)域?qū)儆贠區(qū),此時(shí)仍然有數(shù)據(jù)存放、不同之處在G1已標(biāo)記出這些區(qū)域包含的垃圾最多、也就是回收收益最高的區(qū)域
3、在并發(fā)階段完成之后實(shí)際上O區(qū)的容量變得更大了(O+X的方塊)。這時(shí)因?yàn)檫@個(gè)過(guò)程中發(fā)生了YGC有新的對(duì)象進(jìn)入所致。此外,這個(gè)階段在O區(qū)沒(méi)有回收任何對(duì)象:它的作用主要是標(biāo)記出垃圾最多的區(qū)塊出來(lái)。對(duì)象實(shí)際上是在后面的階段真正開(kāi)始被回收
G1并發(fā)標(biāo)記周期可以分成幾個(gè)階段、其中有些需要暫停應(yīng)用線程。第一個(gè)階段是初始標(biāo)記階段。這個(gè)階段會(huì)暫停所有應(yīng)用線程-部分原因是這個(gè)過(guò)程會(huì)執(zhí)行一次YGC、下面是一個(gè)日志示例:
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]
上面的日志表明發(fā)生了YGC、應(yīng)用線程為此暫停了280毫秒,Eden區(qū)被清空(71MB從Young區(qū)移到了O區(qū))。
日志里面initial-mark的字樣表明后臺(tái)的并發(fā)GC階段開(kāi)始了。因?yàn)槌跏紭?biāo)記階段本身也是要暫停應(yīng)用線程的,
G1正好在YGC的過(guò)程中把這個(gè)事情也一起干了。為此帶來(lái)的額外開(kāi)銷(xiāo)不是很大、增加了20%的CPU,暫停時(shí)間相應(yīng)的略微變長(zhǎng)了些。
接下來(lái),G1開(kāi)始掃描根區(qū)域、日志示例:
50.819: [GC concurrent-root-region-scan-start]
51.408: [GC concurrent-root-region-scan-end, 0.5890230]
一共花了580毫秒,這個(gè)過(guò)程沒(méi)有暫停應(yīng)用線程;是后臺(tái)線程并行處理的。這個(gè)階段不能被YGC所打斷、因此后臺(tái)線程有足夠的CPU時(shí)間很關(guān)鍵。如果Young區(qū)空間恰好在Root掃描的時(shí)候
滿了、YGC必須等待root掃描之后才能進(jìn)行。帶來(lái)的影響是YGC暫停時(shí)間會(huì)相應(yīng)的增加。這時(shí)的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掃描結(jié)束之前就發(fā)生了,表明YGC發(fā)生了等待,等待時(shí)間大概是100毫秒。
在root掃描完成后,G1進(jìn)入了一個(gè)并發(fā)標(biāo)記階段。這個(gè)階段也是完全后臺(tái)進(jìn)行的;GC日志里面下面的信息代表這個(gè)階段的開(kāi)始和結(jié)束:
111.382: [GC concurrent-mark-start]
....
120.905: [GC concurrent-mark-end, 9.5225160 sec]
并發(fā)標(biāo)記階段是可以被打斷的,比如這個(gè)過(guò)程中發(fā)生了YGC就會(huì)。這個(gè)階段之后會(huì)有一個(gè)二次標(biāo)記階段和清理階段:
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]
這兩個(gè)階段同樣會(huì)暫停應(yīng)用線程,但時(shí)間很短。接下來(lái)還有額外的一次并發(fā)清理階段:
120.996: [GC concurrent-cleanup-start]
120.996: [GC concurrent-cleanup-end, 0.0004520]
到此為止,正常的一個(gè)G1周期已完成–這個(gè)周期主要做的是發(fā)現(xiàn)哪些區(qū)域包含可回收的垃圾最多(標(biāo)記為X),實(shí)際空間釋放較少。
混合GC:
接下來(lái)G1執(zhí)行一系列的混合GC。這個(gè)時(shí)期因?yàn)闀?huì)同時(shí)進(jìn)行YGC和清理上面已標(biāo)記為X的區(qū)域,所以稱之為混合階段,下面是一個(gè)混合GC執(zhí)行的前后示意圖:
像普通的YGC那樣、G1完全清空掉Eden同時(shí)調(diào)整survivor區(qū)。另外,兩個(gè)標(biāo)記也被回收了,他們有個(gè)共同的特點(diǎn)是包含最多可回收的對(duì)象,因此這兩個(gè)區(qū)域絕對(duì)部分空間都被釋放了。這兩個(gè)區(qū)域任何存活的對(duì)象都被移到了其他區(qū)域(和YGC存活對(duì)象晉升到O區(qū)類似)。這就是為什么G1的堆比CMS內(nèi)存碎片要少很多的原因–移動(dòng)這些對(duì)象的同時(shí)也就是在壓縮對(duì)內(nèi)存。下面是一個(gè)混合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、但整個(gè)堆的空間釋放內(nèi)存要大于這個(gè)數(shù)目。數(shù)量相差看起來(lái)比較少、只有16MB,但是要考慮同時(shí)有survivor區(qū)的對(duì)象晉升到O區(qū);另外,每次混合GC只是清理一部分的O區(qū)內(nèi)存,整個(gè)GC會(huì)一直持續(xù)到幾乎所有的標(biāo)記區(qū)域垃圾對(duì)象都被回收,這個(gè)階段完了之后G1會(huì)重新回到正常的YGC階段。周期性的,當(dāng)O區(qū)內(nèi)存占用達(dá)到一定數(shù)量之后G1又會(huì)開(kāi)啟一次新的并行GC階段.
原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明:?轉(zhuǎn)載自并發(fā)編程網(wǎng) – ifeve.com
轉(zhuǎn)載于:https://www.cnblogs.com/remagon/p/4440767.html
總結(jié)
以上是生活随笔為你收集整理的[转]深入理解G1垃圾收集器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 文东斋随笔(二)
- 下一篇: Java转iOS-第一个项目总结(2)