1.基礎(chǔ)介紹
1.1 G1簡介
Garbage First(簡稱G1)收集器是垃圾收集器技術(shù)發(fā)展歷史上的里程碑式的成果蛆橡,它開創(chuàng)了收集器面向局部收集的設(shè)計思路和基于Region的內(nèi)存布局形式祠饺。
G1是一款主要面向服務(wù)端應(yīng)用的垃圾收集器错忱。HotSpot開發(fā)團(tuán)隊最初賦予它的期望是(在比較長期的)未來可以替換掉JDK5中發(fā)布的CMS收集器谈秫。JDK9發(fā)布之日,G1宣告取代了Parallel Scavenge加Parallel Old組合,成為服務(wù)端模式下的默認(rèn)垃圾收集器掖鱼,而CMS則淪落被聲明為不推薦(Deprecate)使用的收集器。
G1是一個實現(xiàn)了可控停頓時間的垃圾收集器援制,通過-XX:MaxGCPauseMillis參數(shù)進(jìn)行設(shè)置戏挡,默認(rèn)是200ms。
1.2 Region
G1開創(chuàng)的基于Region的堆內(nèi)存布局晨仑。雖然G1也仍是遵循分代收集理論設(shè)計的褐墅,但其堆內(nèi)存的布局與其他收集器有非常明顯的差異:G1不再堅持固定大小以及固定數(shù)量的分代區(qū)域劃分,而是把連續(xù)的Java堆劃分為多個大小相等的獨立區(qū)域(Region),每一個Region都可以根據(jù)需要洪己,扮演新生代的Eden空間妥凳、Survivor空間,或者老年代空間答捕。收集器能夠?qū)Π缪莶煌巧腞egion采用不同的策略去處理猾封,這樣無論是新創(chuàng)建的對象還是已經(jīng)存活了一段時間、熬過多次收集的舊對象都能獲取很好的收集效果噪珊。
G1中五種不同類型的Region:
- Eden regions(年輕代-Eden區(qū))
- Survivor regions(年輕代-Survivor區(qū))
- Old regions(老年代)
- Humongous regions(巨型對象區(qū)域晌缘,通常也被認(rèn)為是老年代的一部分)
- Free resgions(未分配區(qū)域齐莲,也會叫做可用分區(qū))-上圖中空白的區(qū)域
Region中有一類特殊的Humongous區(qū)域,專門用來存儲大對象磷箕。G1認(rèn)為只要大小超過了一個Region容量一般的對象即可判定為大對象选酗。每個Region的大小可以通過參數(shù)-XX:G1HeapRegionSize設(shè)定,取值范圍為1MB~32MB岳枷,且應(yīng)為2的N次冪芒填。而對于那些超過了整個Region容量的超級大對象,將會被存放在N個連續(xù)的Humongous Region之中空繁,G1的大多數(shù)行為都把Humongous Region作為老年代的一部分來進(jìn)行看待殿衰。
因此,大對象分配空間分配區(qū)域策略可總結(jié)為:
對象大小為:<0.5個Region,存儲到標(biāo)記為Eden的Region中
對象大小為:0.5~1個Region盛泡,存儲到標(biāo)記為Humongous的Region中
對象大小為:>1個Region闷祥,存儲到標(biāo)記為Humongous的多個連續(xù)Region中
1.3 GC類型
- youngGC:回收Eden區(qū)和Survivor區(qū)的內(nèi)存。
- MixedGC:包含所有年輕代以及部分老年代Region傲诵。
- FullGC:全堆掃描凯砍。
2.運(yùn)行原理
2.1 基礎(chǔ)知識
2.1.1 記憶集(Remenbered Set,簡稱RSet)和卡表(Card Table)
記憶集(RSet)是一種用于記錄從非收集區(qū)域指向收集區(qū)域的指針集合的抽象數(shù)據(jù)結(jié)構(gòu)∷┲瘢卡表(Card Table)就是記憶集的一種具體實現(xiàn)悟衩,它定義了記憶集的記憶精度、與堆內(nèi)存的映射關(guān)系等栓拜。關(guān)于卡表與記憶集的關(guān)系座泳,可以用HashMap與Map的關(guān)系來類比理解。
卡表最簡單的形式可以只是一個字節(jié)數(shù)組幕与,數(shù)組中的每一個元素都對應(yīng)著其標(biāo)識的內(nèi)存區(qū)域中一塊特定大小的內(nèi)存塊钳榨,整個內(nèi)存塊被稱為“卡頁”(Card Page)。HotSpot中卡頁大小為521字節(jié)纽门。如果卡表標(biāo)識內(nèi)存區(qū)域的起始地址是)0x0000的話,數(shù)組的第0营罢、1赏陵、2號元素,分別對應(yīng)了地址范圍0x0000 ~ 0x01FF饲漾、0x0200 ~ 0x03FF蝙搔、0x0400 ~ 0x05FF的卡頁內(nèi)存塊,如下圖所示考传。
一個卡頁的內(nèi)存中通常包含不止一個對象吃型,只要卡頁內(nèi)有一個(或更多)對象的字段存在著跨代指針,那就對應(yīng)卡表的數(shù)組元素的值標(biāo)識為1僚楞,成為這個元素變臟勤晚,沒有則標(biāo)識為0.在垃圾收集發(fā)生時枉层,只要篩選出卡表中變臟的元素,舊能輕易得出哪些卡頁內(nèi)存塊中包含跨代指針赐写,把它們加入GC Roots中一并掃描鸟蜡。
因此,RSet存在的意義就是避免對象跨代引用時對整個堆內(nèi)存對象的掃描挺邀,起到一種類似索引(更像空間索引)的作用揉忘。
2.1.2 原始快照(Snapshot At The Beginning,SATB)
簡潔地來說端铛,SATB是維持并發(fā)GC的一種手段泣矛,因為像CMS、G1等收集器禾蚕,首先經(jīng)過初始標(biāo)記您朽,然后進(jìn)行并發(fā)標(biāo)記,并發(fā)標(biāo)記過程中夕膀,可能對初始標(biāo)記的結(jié)果產(chǎn)生了改動虚倒,需要進(jìn)行修正,分別有增量更新和原始快照兩種解決方法产舞。CMS收集器使用增量更新來糾正對象引用關(guān)系魂奥,而G1收集器使用原始快照的策略。
具體參考:
- http://www.reibang.com/p/8d37a07277e0
- 《深入理解Java虛擬機(jī)第三版》p89頁
2.2 運(yùn)行流程
2.1.1 初始標(biāo)記
僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象易猫,并且修改TAMS指針的值耻煤,讓下一階段用戶線程并發(fā)運(yùn)行時,能正確地在可用地Region中分配新對象准颓。這個階段需要停頓線程(會產(chǎn)生STW)哈蝇,而且是借用進(jìn)行Minor GC地時候同步完成的,所以G1收集器在這個階段沒有產(chǎn)生特別明顯的停頓攘已。
2.2.2 并發(fā)標(biāo)記
從GC Root開始對堆中對象進(jìn)行可達(dá)性分析炮赦,集合RSet,遞歸掃描堆里的對象圖样勃,找出要回收的對象吠勘,這個階段耗時較長,但可與用戶程序并發(fā)執(zhí)行峡眶。當(dāng)對象掃描完成以后剧防,還要重新處理SATB記錄下的在并發(fā)時有引用變動的對象。
2.2.3 最終標(biāo)記(重新標(biāo)記)
對用戶線程做另一個短暫的暫停(會產(chǎn)生STW)辫樱,用于處理并發(fā)階段結(jié)束后仍遺留下來的最后少量的SATB記錄峭拘。
2.2.4 篩選回收
負(fù)責(zé)更新Region的統(tǒng)計數(shù)據(jù),對各個Region的回收價值和成本進(jìn)行排序,根據(jù)用戶所期望的停頓時間來制定回收計劃鸡挠,可以自由選擇任意多個Region構(gòu)成回收集辉饱,然后決定回收的那一部分Region的存貨對象復(fù)制到空的Region中,再清理掉整個舊Region的全部空間宵凌。這里的曹祖喲涉及存活對象的移動鞋囊,是必須暫停用戶線程,由多條收集器線程并行完成的瞎惫。
3.總結(jié)
G1除了并發(fā)標(biāo)記外溜腐,其余階段也是要完全暫停用戶線程的,換言之瓜喇,它并非純粹地追求低延遲挺益,官方給它設(shè)定的目標(biāo)是在延遲可控的情況下獲得盡可能高的吞吐量,所以才能擔(dān)當(dāng)起“全功能收集器”的重任與期望乘寒。
G1提高效率的點有哪些:
- 使用RSet降低了掃描的范圍望众。
- 重新標(biāo)記階段使用SATB速度比CMS的增量更新快。
- 清理過程中伞辛,選擇部分回收價值高的Region進(jìn)行清理(mixedGC)烂翰,而不是所有Region,提高了清理效率蚤氏。
4. 參考
- https://www.bilibili.com/video/BV13J411g7A1?from=search&seid=3010204080299746856
- 《深入理解Java虛擬機(jī)第三版》