5.7 G1收集器
G1是一款面向服務(wù)端應(yīng)用的垃圾收集器。HotSpot開(kāi)發(fā)團(tuán)隊(duì)賦予它的使命是未來(lái)可以替換JDK1.5中發(fā)布的CMS收集器。與其他收集器相比,G1具備如下特點(diǎn):
并行于并發(fā):能充分利用多
CPU、多核環(huán)境下的硬件優(yōu)勢(shì),使用多個(gè)CPU來(lái)縮短Stop The World停頓的時(shí)間,部分其他收集器原本需要停頓Java線(xiàn)程執(zhí)行的GC動(dòng)作,G1收集器仍然可以通過(guò)并發(fā)的方式讓Java線(xiàn)程繼續(xù)執(zhí)行。分代收集:與其他收集器一樣,分代概念仍然在
G1中依然得以保留。雖然G1可以不需要其他收集器配合就能獨(dú)立管理整個(gè)GC堆,但它能夠采用不同的方式處理創(chuàng)建的對(duì)象和已經(jīng)存活了一段時(shí)間、熬過(guò)多次GC的舊對(duì)象以獲取更好的收集效果。空間整合:
G1從整體來(lái)看是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器,從局部(兩個(gè)Region之間)上來(lái)看是基于“復(fù)制”算法實(shí)現(xiàn)的,但無(wú)論如何,這兩種算法都意味著G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片,收集后能提供規(guī)整的可用內(nèi)存。這種特性有利于程序長(zhǎng)時(shí)間運(yùn)行,分配大對(duì)象時(shí)不會(huì)因?yàn)闊o(wú)法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次GC。可預(yù)測(cè)的停頓:這是
G1相對(duì)于CMS的另一大優(yōu)勢(shì),降低停頓時(shí)間是G1和CMS共同的關(guān)注點(diǎn),但G1除了追求低停頓外,還能建立可預(yù)測(cè)的停頓時(shí)間模型,能讓使用者明確指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過(guò)N毫秒,這幾乎已經(jīng)是實(shí)時(shí)Java(RTSJ)的垃圾收集器的特征了。
在G1之前的其他收集器進(jìn)行收集的范圍都是整個(gè)新生代或者老年代,而G1不再是這樣。使用G1收集器時(shí),Java堆的內(nèi)存布局就是與其他收集器有很大差別,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離了,它們都是一部分Region(不再要連續(xù))的集合。
G1收集器之所以能建立可預(yù)測(cè)的停頓時(shí)間模型,是因?yàn)樗梢杂杏?jì)劃地避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集。G1跟蹤各個(gè)Region里面的垃圾堆積的價(jià)值大?。〞?huì)后所獲得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值),在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間,優(yōu)先回收價(jià)值最大的Region。這種使用Region劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式,保證了G1收集器在有限的時(shí)間內(nèi)可以獲取盡可能高的收集效率。
G1把內(nèi)存“化整為零”的思路,理解起來(lái)似乎很容易,但其中的實(shí)現(xiàn)細(xì)節(jié)卻遠(yuǎn)遠(yuǎn)沒(méi)有那樣簡(jiǎn)單。比如,把Java堆分為多個(gè)Region后,垃圾收集是否就真的能以Region為單位進(jìn)行了?聽(tīng)起來(lái)順理成章,再仔細(xì)想想就很容易發(fā)現(xiàn)問(wèn)題所在:Region不可能是孤立的。一個(gè)對(duì)象分配在某個(gè)Region中,它并非只能本Region中的其他對(duì)象引用,而是可以與整個(gè)Java堆任意的對(duì)象發(fā)生引用關(guān)系。那在做可達(dá)性判定確定對(duì)象是否存活的時(shí)候,豈不是還得掃描整個(gè)Java堆才能保證準(zhǔn)確性?這個(gè)問(wèn)題起始并非在G1中才有,只是在G1中更加突出而已。在以前的分代收集中,新生代的規(guī)模一般都比老年代要小許多,新生代的收集也比老年代要頻繁許多,那回收新生代中的對(duì)象時(shí)也面臨相同的問(wèn)題,如果回收新生代時(shí)也不得不同時(shí)掃描老年代的話(huà),那么Minor GC的效率可能下降不少。
在G1收集器中,Region之間的對(duì)象引用以及其他收集器中的新生代與老年代之間的對(duì)象引用,虛擬機(jī)都是使用Remembered Set來(lái)避免全堆掃描的。G1中每個(gè)Region都有一個(gè)與之對(duì)應(yīng)的Remembered Set,虛擬機(jī)發(fā)現(xiàn)程序在對(duì)Reference類(lèi)型的數(shù)據(jù)進(jìn)行寫(xiě)操作時(shí),會(huì)產(chǎn)生一個(gè)Write Barrier暫時(shí)中斷寫(xiě)操作,檢查Reference引用的對(duì)象是否處于不同的Region之中(在分代的例子中就是檢查是否老年代中的對(duì)象引用了新生代的對(duì)象),如果是,便通過(guò)CardTable把相關(guān)引用信息記錄到被引用對(duì)象所屬的Region的Remembered Set之中。當(dāng)進(jìn)行內(nèi)存回收時(shí),在GC根節(jié)點(diǎn)的枚舉范圍中加入Remembered Set即可保證不對(duì)全堆掃描也不會(huì)有遺漏。
如果不計(jì)算維護(hù)Remembered Set的操作,G1收集器的運(yùn)作大致可劃分為以下幾個(gè)步驟:
- 初始標(biāo)記(
Initial Marking) - 并發(fā)標(biāo)記(
Concurrent Marking) - 最終標(biāo)記(
Final Marking) - 篩選回收(
Live Data Counting and Evacuation)
此收集器的前幾個(gè)步驟和CMS很相似,初始標(biāo)記階段只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,并且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶(hù)程序并發(fā)執(zhí)行時(shí),能在正確可用的Region中創(chuàng)建新對(duì)象,這個(gè)階段需要停頓線(xiàn)程,但耗時(shí)很短。
并發(fā)標(biāo)記是從GC Roots開(kāi)始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,找出存活的對(duì)象,此階段耗時(shí)較長(zhǎng),但可與用戶(hù)程序并發(fā)執(zhí)行。
最終標(biāo)記階段則是為了修正在并發(fā)標(biāo)記期間用戶(hù)程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分標(biāo)記記錄,虛擬機(jī)將這段時(shí)間對(duì)象變化記錄在線(xiàn)程Remembered Set Logs里面,最終標(biāo)記階段需要把Remembered Set Logs的數(shù)據(jù)合并到Remembered Set中,這個(gè)階段需要停頓線(xiàn)程,但是可以并發(fā)執(zhí)行。
篩選回收階段首先對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排行,根據(jù)用戶(hù)所期望的GC停頓時(shí)間來(lái)指定回收計(jì)劃,但是因?yàn)橹换厥找徊糠?code>Region,時(shí)間是用戶(hù)可控制的,而且停頓用戶(hù)線(xiàn)程將大幅提高收集效率,具體執(zhí)行過(guò)程如下圖。

5.8 理解 GC 日志
每一種收集器的日志形式都是由它們自身的實(shí)現(xiàn)所決定的,也就是說(shuō),每個(gè)收集器的日志格式都可以不一樣。下面看一個(gè)典型的GC日志:
5.617: [GC 5.617: [ParNew: 43296K->7006K(47808K),
0.0136826 secs] 44992K->8702K(252608K), 0.0137904 secs]
[Times: user=0.03 sys=0.00, real=0.02 secs]
說(shuō)明:
5.617(時(shí)間戳): [GC(Young GC,收集器類(lèi)型) 5.617(時(shí)間戳):
[ParNew(使用ParNew作為年輕代的垃圾回收期): 43296K(年輕代垃圾回收前的大小)->
7006K(年輕代垃圾回收以后的大?。?47808K)(年輕代的總大?。? 0.0136826 secs(回收時(shí)間)]
44992K(堆區(qū)垃圾回收前的大小)->8702K(堆區(qū)垃圾回收后的大?。?252608K)(堆區(qū)總大?。?
0.0137904 secs(回收時(shí)間)] [Times: user=0.03(Young GC用戶(hù)耗時(shí))
sys=0.00(Young GC系統(tǒng)耗時(shí)), real=0.02 secs(Young GC實(shí)際耗時(shí))]
再看一個(gè)例子:
7.429: [GC 7.429: [ParNew: 45278K->6723K(47808K),
0.0251993 secs] 46974K->10551K(252608K), 0.0252421 secs]
說(shuō)明:從GC記錄中我們可以看到Young GC回收了45278-6723=38555K的內(nèi)存。Heap區(qū)通過(guò)這次回收總共減少了 46974-10551=36423K的內(nèi)存。38555-36423=2132K說(shuō)明通過(guò)該次Young GC有2132K的內(nèi)存被移動(dòng)到了Old Gen。
5.9 垃圾收集器參數(shù)總結(jié)
| 參數(shù) | 描述 |
|---|---|
-XX:+UseSerialGC |
Jvm運(yùn)行在Client模式下的默認(rèn)值,打開(kāi)此開(kāi)關(guān)后,使用Serial + Serial Old的收集器組合進(jìn)行內(nèi)存回收 |
-XX:+UseParNewGC |
打開(kāi)此開(kāi)關(guān)后,使用ParNew + Serial Old的收集器進(jìn)行垃圾回收 |
-XX:+UseConcMarkSweepGC |
使用ParNew + CMS + Serial Old的收集器組合進(jìn)行內(nèi)存回收,Serial Old作為CMS出現(xiàn)“Concurrent Mode Failure”失敗后的后備收集器使用 |
-XX:+UseParallelGC |
Jvm運(yùn)行在Server模式下的默認(rèn)值,打開(kāi)此開(kāi)關(guān)后,使用Parallel Scavenge + Serial Old的收集器組合進(jìn)行回收 |
-XX:+UseParallelOldGC |
使用Parallel Scavenge + Parallel Old的收集器組合進(jìn)行回收 |
-XX:SurvivorRatio |
新生代中Eden區(qū)域與Survivor區(qū)域的容量比值,默認(rèn)為8,代表Eden:Subrvivor = 8:1
|
-XX:PretenureSizeThreshold |
直接晉升到老年代對(duì)象的大小,設(shè)置這個(gè)參數(shù)后,大于這個(gè)參數(shù)的對(duì)象將直接在老年代分配 |
-XX:MaxTenuringThreshold |
晉升到老年代的對(duì)象年齡,每次Minor GC之后,年齡就加1,當(dāng)超過(guò)這個(gè)參數(shù)的值時(shí)進(jìn)入老年代 |
-XX:UseAdaptiveSizePolicy |
動(dòng)態(tài)調(diào)整java堆中各個(gè)區(qū)域的大小以及進(jìn)入老年代的年齡 |
-XX:+HandlePromotionFailure |
是否允許新生代收集擔(dān)保,進(jìn)行一次minor gc后, 另一塊Survivor空間不足時(shí),將直接會(huì)在老年代中保留 |
-XX:ParallelGCThreads |
設(shè)置并行GC進(jìn)行內(nèi)存回收的線(xiàn)程數(shù) |
-XX:GCTimeRatio |
GC時(shí)間占總時(shí)間的比列,默認(rèn)值為99,即允許1%的GC時(shí)間,僅在使用Parallel Scavenge 收集器時(shí)有效 |
-XX:MaxGCPauseMillis |
設(shè)置GC的最大停頓時(shí)間,在Parallel Scavenge收集器下有效 |
-XX:CMSInitiatingOccupancyFraction |
設(shè)置CMS收集器在老年代空間被使用多少后出發(fā)垃圾收集,默認(rèn)值為68%,僅在CMS收集器時(shí)有效,-XX:CMSInitiatingOccupancyFraction=70
|
-XX:+UseCMSCompactAtFullCollection |
由于CMS收集器會(huì)產(chǎn)生碎片,此參數(shù)設(shè)置在垃圾收集器后是否需要一次內(nèi)存碎片整理過(guò)程,僅在CMS收集器時(shí)有效 |
-XX:+CMSFullGCBeforeCompaction |
設(shè)置CMS收集器在進(jìn)行若干次垃圾收集后再進(jìn)行一次內(nèi)存碎片整理過(guò)程,通常與UseCMSCompactAtFullCollection參數(shù)一起使用 |
-XX:+UseFastAccessorMethods |
原始類(lèi)型優(yōu)化 |
-XX:+DisableExplicitGC |
是否關(guān)閉手動(dòng)System.gc()
|
-XX:+CMSParallelRemarkEnabled |
降低標(biāo)記停頓 |
-XX:LargePageSizeInBytes |
內(nèi)存頁(yè)的大小不可設(shè)置過(guò)大,會(huì)影響Perm的大小,-XX:LargePageSizeInBytes=128m
|
–XX:+UseG1GC |
打開(kāi)此開(kāi)關(guān)后,使用G1垃圾收集器 |