G1
垃圾收集器的設(shè)計原則是“首先收集盡可能多的垃圾(Garbage First)”,目標(biāo)是為了盡量縮短處理超大堆(超過4GB)產(chǎn)生的停頓。
因此榄审,G1
并不會等內(nèi)存耗盡(比如Serial
串行收集器驳概、Parallel
并行收集器 )者快耗盡(CMS
)的時候才開始垃圾回收,而是在內(nèi)部采用了啟發(fā)式算法旅薄,在老年代中找出具有高收集收益的分區(qū)(Region
)進(jìn)行收集贡歧。
同時 G1
可以根據(jù)用戶設(shè)置的STW
(Stop-The-World)停頓時間目標(biāo)(響應(yīng)時間優(yōu)先)自動調(diào)整年輕代和總堆的大小,停頓時間越短年輕代空間就可能越小赋秀,總堆空間越大利朵。
G1
相對于CMS
一個比較明顯的優(yōu)勢是,內(nèi)存碎片的產(chǎn)生率大大降低猎莲。
G1
在 JDK7u4以上都可以使用绍弟,在JDK9開始,G1
為默認(rèn)的垃圾收集器著洼,以替代CMS
樟遣。
G1算法
算法:三色標(biāo)記 + SATB
G1的特性
- 面向服務(wù)端應(yīng)用的垃圾收集器
- 并行與并發(fā):G1能充分利用多CPU、多核環(huán)境使用多個CPU或CPU核心來縮短
STW
(Stop-The-World)停頓時間身笤。 - 分代收集:G1物理上不分代豹悬,但邏輯上仍然有分代的概念。
- 空間整合:不會產(chǎn)生內(nèi)存空間碎片液荸,收集后可提供規(guī)整的可用內(nèi)存瞻佛,整理空閑空間更快。
- 可預(yù)測的停頓(它可以有計劃的避免在整個JAVA堆中進(jìn)行全區(qū)域的垃圾收集)
- 適用于不需要實現(xiàn)很高吞吐量的場景
- JAVA堆內(nèi)存布局與其它收集器存在很大差別,它將整個JAVA堆劃分為多個大小相等的獨立區(qū)域或分區(qū)(
Region
)伤柄。 - G1收集器中绊困,虛擬機(jī)使用
Remembered Set
來避免全堆掃描。
G1的內(nèi)存模型
分區(qū)概念
傳統(tǒng)的GC收集器將連續(xù)的內(nèi)存空間劃分為新生代适刀、老年代和永久代(JDK 8去除了永久代秤朗,引入了元空間Metaspace),
這種劃分的特點是各代的存儲地址(邏輯地址笔喉,下同)是連續(xù)的取视。如下圖所示:
而G1的各代存儲地址是不連續(xù)的,每一代都使用了n個不連續(xù)的大小相同的Region
常挚,每個Region
占有一塊連續(xù)的虛擬內(nèi)存地址贫途。
Region
(區(qū)域,分區(qū))
G1采用了分區(qū)(Region
)的思路待侵,將整個堆空間分成若干個大小相等的內(nèi)存區(qū)域丢早,每次分配對象空間將逐段地使用內(nèi)存。
雖然還保留了新生代和老年代的概念秧倾,但新生代和老年代不再是物理隔離怨酝,它們都是一部分Region
(不需要連續(xù))的集合。
因此那先,在堆的使用上农猬,G1并不要求對象的存儲一定是物理上連續(xù)的,只要邏輯上連續(xù)即可售淡;
每個分區(qū)Region
也不會確定地為某個代服務(wù)斤葱,可以按需在年輕代和老年代之間切換。
啟動時可以通過參數(shù)-XX:G1HeapRegionSize=n
可指定分區(qū)大小(1MB~32MB揖闸,且必須是2的冪)揍堕,默認(rèn)將整堆劃分為2048個分區(qū)。
Card
(卡片)
在每個分區(qū)Region
內(nèi)部又被分成了若干個大小為512 Byte卡片(Card)汤纸,標(biāo)識堆內(nèi)存最小可用粒度衩茸。
所有分區(qū)Region
的卡片將會記錄在全局卡片表(Global Card Table
)中。
分配的對象會占用物理上連續(xù)的若干個卡片贮泞。
當(dāng)查找對分區(qū)Region
內(nèi)對象的引用時便可通過記錄卡片來查找該引用對象(見RSet
)楞慈。
每次對內(nèi)存的回收,都是對指定分區(qū)的卡片進(jìn)行處理啃擦。
Heap
(堆)
G1同樣可以通過-Xms/-Xmx
來指定堆空間大小囊蓝。
當(dāng)發(fā)生年輕代收集(YGC
)或混合收集(Mixed GC
)時,通過計算GC與應(yīng)用的耗費時間比令蛉,自動調(diào)整堆空間大小聚霜。
如果GC頻率太高,則通過增加堆尺寸,來減少GC頻率俯萎,相應(yīng)地GC占用的時間也隨之降低;
目標(biāo)參數(shù)-XX:GCTimeRatio
即為GC與應(yīng)用的耗費時間比运杭,G1默認(rèn)為12(JDK7,8為99夫啊,JDK11+開始為12),而CMS默認(rèn)為99辆憔,因為CMS的設(shè)計原則是耗費在GC上的時間盡可能的少撇眯。
另外,當(dāng)空間不足虱咧,如對象空間分配或轉(zhuǎn)移失敗時熊榛,G1會首先嘗試增加堆空間,如果擴(kuò)容失敗腕巡,則發(fā)起擔(dān)保的Full GC
玄坦。
Full GC
后,堆尺寸計算結(jié)果也會調(diào)整堆空間绘沉。
分代概念
Generation
(分代 )
分代垃圾收集可以將關(guān)注點集中在最近被分配的對象上煎楣,而無需整堆掃描,避免長命對象的拷貝车伞,同時獨立收集有助于降低響應(yīng)時間择懂。
雖然分區(qū)使得內(nèi)存分配不再要求緊湊的內(nèi)存空間,但G1依然使用了分代的思想另玖。
與其他垃圾收集器類似困曙,G1將內(nèi)存在邏輯上劃分為年輕代和老年代,其中年輕代又劃分為Eden空間和Survivor空間谦去。
但年輕代空間并不是固定不變的慷丽,當(dāng)現(xiàn)有年輕代分區(qū)占滿時,JVM會分配新的空閑分區(qū)加入到年輕代空間鳄哭。
整個年輕代內(nèi)存會在初始空間-XX:NewSize
與最大空間-XX:MaxNewSize
之間動態(tài)變化盈魁,且由參數(shù)目標(biāo)暫停時間-XX:MaxGCPauseMillis
、需要擴(kuò)縮容的大小以及分區(qū)的已記憶集合(RSet
)計算得到窃诉。
當(dāng)然杨耙,G1依然可以設(shè)置固定的年輕代大小(參數(shù)
-XX:NewRatio
、-Xmn
)飘痛,但同時暫停目標(biāo)將失去意義珊膜。
Local allocation buffer
(LAB
) (本地分配緩沖)
值得注意的是,由于分區(qū)的思想宣脉,每個線程均可以"認(rèn)領(lǐng)"某個分區(qū)Region
用于線程本地的內(nèi)存分配漆枚,而不需要顧及分區(qū)是否連續(xù)呻袭。
因此飒泻,每個應(yīng)用線程和GC線程都會獨立的使用分區(qū)阻课,進(jìn)而減少同步時間,提升GC效率习寸,這個分區(qū)Region
稱為本地分配緩沖區(qū)(LAB
)。
- 應(yīng)用線程本地緩沖區(qū)
TLAB
:
應(yīng)用線程可以獨占一個本地緩沖區(qū)(TLAB
)來創(chuàng)建的對象,而大部分都會落入Eden區(qū)域(巨型對象或分配失敗除外)感憾,因此TLAB的分區(qū)屬于Eden空間; - GC線程本地緩沖區(qū)
GCLAB
:
每次垃圾收集時令花,每個GC線程同樣可以獨占一個本地緩沖區(qū)(GCLAB
)用來轉(zhuǎn)移對象阻桅,每次回收會將對象復(fù)制到Suvivor空間或老年代空間; - 晉升本地緩沖區(qū)
PLAB
:
對于從Eden/Survivor空間晉升(Promotion)到Survivor/老年代空間的對象兼都,同樣有GC獨占的本地緩沖區(qū)進(jìn)行操作嫂沉,該部分稱為晉升本地緩沖區(qū)(PLAB
)。
分區(qū)模型
Humongous Object
(巨型對象)
一個大小達(dá)到甚至超過分區(qū)Region
50%以上的對象稱為巨型對象(Humongous Object
)扮碧。
巨型對象會獨占一個趟章、或多個連續(xù)分區(qū),其中第一個分區(qū)被標(biāo)記為開始巨型(StartsHumongous
)慎王,相鄰連續(xù)分區(qū)被標(biāo)記為連續(xù)巨型(ContinuesHumongous
)尤揣。
Humongous Object
有以下特點:
-
Humongous Object
直接分配到了 老年代,防止了反復(fù)拷貝移動柬祠。
當(dāng)線程為巨型分配空間時北戏,不能簡單在
TLAB
進(jìn)行分配,因為巨型對象的移動成本很高漫蛔,而且有可能一個分區(qū)不能容納巨型對象嗜愈。
因此,巨型對象會直接在老年代分配莽龟,所占用的連續(xù)空間稱為巨型分區(qū)(Humongous Region
)蠕嫁。
-
Humongous Object
在YGC
階段,Global Concurrent Marking
階段的Cleanup
和FGC
階段 回收毯盈。
由于無法享受
LAB
帶來的優(yōu)化剃毒,并且確定一片連續(xù)的內(nèi)存空間需要掃描整堆Heap
,因此確定巨型對象開始位置的成本非常高搂赋,如果可以赘阀,應(yīng)用程序應(yīng)避免生成巨型對象。
- 在分配
Humongous Object
之前先檢查是否超過 initiating heap occupancy percent (由參數(shù)-XX:InitiatingHeapOccupancyPercent
控制) 和 the marking threshold脑奠。
如果超過的話基公,就啟動并發(fā)收集周期Concurrent Marking Cycle
,為的是提早回收宋欺,防止Evacuation Failure
和Full GC
轰豆。
RSet
(Remember Set
胰伍,已記憶集合)
在串行和并行收集器中,GC通過整堆掃描酸休,來確定對象是否處于可達(dá)路徑中骂租。
然而G1為了避免STW
式的整堆Heap
掃描,在每個分區(qū)Region
記錄了一個已記憶集合(RSet
)斑司,內(nèi)部類似一個反向指針渗饮,記錄引用分區(qū)Region
內(nèi)對象的卡片Card
的索引。
當(dāng)要回收該分區(qū)Region
時陡厘,通過掃描分區(qū)的RSet抽米,來確定引用本分區(qū)內(nèi)的對象是否存活特占,進(jìn)而確定本分區(qū)內(nèi)的對象存活情況糙置。
事實上,并非所有的引用都需要記錄在RSet
中是目,如果一個分區(qū)Region
確定需要掃描谤饭,那么無需RSet
也可以無遺漏的得到引用關(guān)系。
那么引用源自本分區(qū)Region
的對象懊纳,當(dāng)然不用落入RSet
中揉抵;
同時,G1 GC每次都會對年輕代進(jìn)行整體收集嗤疯,因此引用源自年輕代的對象冤今,也不需要在RSet
記錄。
最后只有老年代的分區(qū)Region
可能會有RSet記錄茂缚,這些分區(qū)稱為擁有RSet分區(qū)(an RSet’s owning region)戏罢。
Per Region Table
(PRT)
RSet
在內(nèi)部使用Per Region Table
(PRT)記錄分區(qū)Region
的引用情況。
由于RSet
的記錄要占用分區(qū)Region
的空間脚囊,如果一個分區(qū)非常"受歡迎"龟糕,那么RSet
占用的空間會上升,從而降低分區(qū)Region
的可用空間悔耘。
G1應(yīng)對這個問題采用了改變RSet
的密度的方式讲岁,在PRT
中將會以三種模式記錄引用:
- 稀少:直接記錄引用對象的卡片
Card
的索引 - 細(xì)粒度:記錄引用對象的分區(qū)
Region
的索引 - 粗粒度:只記錄引用情況,每個分區(qū)對應(yīng)一個比特位
由上可知衬以,粗粒度的PRT
只是記錄了引用數(shù)量缓艳,需要通過整堆Heap
掃描才能找出所有引用,因此掃描速度也是最慢的看峻。
CSet
(Collection Set
郎任,收集集合)
收集集合(CSet
)代表每次GC暫停時回收的一系列目標(biāo)分區(qū)Region
。
在任意一次收集暫停中备籽,CSet
所有分區(qū)都會被釋放舶治,內(nèi)部存活的對象都會被轉(zhuǎn)移到分配的空閑分區(qū)中分井。
因此無論是年輕代收集,還是混合收集霉猛,工作的機(jī)制都是一致的尺锚。
年輕代收集(YGC
)的CSet
只容納年輕代分區(qū),而混合收集(Mixed GC
)會通過啟發(fā)式算法惜浅,在老年代候選回收分區(qū)中瘫辩,篩選出回收收益最高的分區(qū)添加到CSet
中。
候選老年代分區(qū)的
CSet
準(zhǔn)入條件坛悉,可以通過活躍度閾值-XX:G1MixedGCLiveThresholdPercent
(默認(rèn)85%)進(jìn)行設(shè)置伐厌,從而攔截那些回收開銷巨大的對象;同時裸影,每次混合收集可以包含候選老年代分區(qū)挣轨,可根據(jù)
CSet
對堆的總大小占比-XX:G1OldCSetRegionThresholdPercent
(默認(rèn)10%)設(shè)置數(shù)量上限。
由上述可知轩猩,G1的收集都是根據(jù)CSet
進(jìn)行操作的卷扮,年輕代收集(YGC
)與混合收集(Mixed GC
)沒有明顯的不同,最大的區(qū)別在于兩種收集的觸發(fā)條件均践。
年輕代收集集合 CSet of Young Collection
應(yīng)用線程不斷活動后晤锹,年輕代空間會被逐漸填滿。當(dāng)JVM分配對象到Eden區(qū)域失敗(Eden區(qū)已滿)時彤委,便會觸發(fā)一次STW
式的年輕代收集鞭铆。
在年輕代收集中,Eden分區(qū)存活的對象將被拷貝到Survivor分區(qū)焦影;
原有Survivor分區(qū)存活的對象车遂,將根據(jù)任期閾值(tenuring threshold)分別晉升到PLAB
中,新的survivor分區(qū)和老年代分區(qū)偷办。而原有的年輕代分區(qū)將被整體回收掉艰额。
同時,年輕代收集還負(fù)責(zé)維護(hù)對象的年齡(存活次數(shù))椒涯,輔助判斷老化(tenuring)對象晉升的時候是到Survivor分區(qū)還是到老年代分區(qū)柄沮。
年輕代收集首先先將晉升對象尺寸總和、對象年齡信息維護(hù)到年齡表中废岂,再根據(jù)年齡表祖搓、Survivor尺寸、Survivor填充容量-XX:TargetSurvivorRatio
(默認(rèn)50%)湖苞、最大任期閾值-XX:MaxTenuringThreshold
(默認(rèn)15)拯欧,計算出一個恰當(dāng)?shù)娜纹陂撝担彩浅^任期閾值的對象都會被晉升到老年代财骨。
混合收集集合 CSet of Mixed Collection
年輕代收集不斷活動后镐作,老年代的空間也會被逐漸填充藏姐。當(dāng)老年代占用空間超過整堆比IHOP閾值-XX:InitiatingHeapOccupancyPercent
(默認(rèn)45%)時,G1就會啟動一次混合垃圾收集周期该贾。
為了滿足暫停目標(biāo)羔杨,G1可能不能一口氣將所有的候選分區(qū)收集掉,因此G1可能會產(chǎn)生連續(xù)多次的混合收集與應(yīng)用線程交替執(zhí)行杨蛋,每次STW
的混合收集與年輕代收集過程相類似兜材。
- 為了確定包含到年輕代收集集合CSet的老年代分區(qū),JVM通過參數(shù)混合周期的最大總次數(shù)
-XX:G1MixedGCCountTarget
(默認(rèn)8)逞力、堆廢物百分比-XX:G1HeapWastePercent
(默認(rèn)5%)曙寡。
通過候選老年代分區(qū)總數(shù)與混合周期最大總次數(shù),確定每次包含到CSet
的最小分區(qū)數(shù)量寇荧;
根據(jù)堆廢物百分比举庶,當(dāng)收集達(dá)到參數(shù)時,不再啟動新的混合收集砚亭。而每次添加到CSet
的分區(qū)灯变,則通過計算得到的GC效率進(jìn)行安排殴玛。
G1的活動周期
G1的垃圾回收包括了以下幾種:
Concurrent Marking Cycle (并發(fā)收集)
類似CMS
的并發(fā)收集過程捅膘。Young Collection (YGC,年輕代收集滚粟,
STW
)Mixed Collection Cycle (混合收集寻仗,
STW
)Full GC(FGC,
STW
)
JDK10以前FGC是串行回收凡壤,JDK10+可以是并行回收署尤。
并發(fā)標(biāo)記周期 Concurrent Marking Cycle
并發(fā)標(biāo)記周期是G1中非常重要的階段,這個階段將會為混合收集周期識別垃圾最多的老年代分區(qū)亚侠。
整個周期完成根標(biāo)記曹体、識別所有(可能)存活對象,并計算每個分區(qū)的活躍度硝烂,從而確定GC效率等級箕别。
當(dāng)達(dá)到IHOP閾值-XX:InitiatingHeapOccupancyPercent
(老年代占整堆比,默認(rèn)45%)時滞谢,便會觸發(fā)并發(fā)標(biāo)記周期串稀。
整個并發(fā)標(biāo)記周期將由初始標(biāo)記(Initial Mark)、根分區(qū)掃描(Root Region Scanning)狮杨、并發(fā)標(biāo)記(Concurrent Marking)母截、重新標(biāo)記(Remark)、清除(Cleanup)幾個階段組成橄教。
其中清寇,初始標(biāo)記(隨年輕代收集一起活動)喘漏、重新標(biāo)記、清除是STW的华烟,而并發(fā)標(biāo)記如果來不及標(biāo)記存活對象陷遮,則可能在并發(fā)標(biāo)記過程中,G1又觸發(fā)了幾次年輕代收集(YGC
)垦江。
Initial Marking
(初始標(biāo)記帽馋, STW)
它標(biāo)記了從GC Root開始直接可達(dá)的對象。
事實上比吭,當(dāng)達(dá)到IHOP閾值時绽族,G1并不會立即發(fā)起并發(fā)標(biāo)記周期,而是等待下一次年輕代收集衩藤,利用年輕代收集的STW時間段吧慢,完成初始標(biāo)記,這種方式稱為借道(Piggybacking)赏表。
Root region scanning
(根分區(qū)掃描)
在初始標(biāo)記暫停結(jié)束后检诗,年輕代收集也完成的對象復(fù)制到Survivor的工作,應(yīng)用線程開始活躍起來瓢剿。此時為了保證標(biāo)記算法的正確性逢慌,所有新復(fù)制到Survivor分區(qū)的對象,都需要被掃描并標(biāo)記成根间狂,這個過程稱為根分區(qū)掃描(Root Region Scanning)攻泼,同時掃描的Suvivor分區(qū)也被稱為根分區(qū)(Root Region)。
Concurrent Marking
(并發(fā)標(biāo)記)
這個階段從GC Root開始對heap中的對象標(biāo)記鉴象,標(biāo)記線程與應(yīng)用程序線程并行執(zhí)行忙菠,并且收集各個Region
的存活對象信息。
和應(yīng)用線程并發(fā)執(zhí)行纺弊,并發(fā)標(biāo)記線程在并發(fā)標(biāo)記階段啟動牛欢,由參數(shù)-XX:ConcGCThreads
(默認(rèn)GC線程數(shù)的1/4,即-XX:ParallelGCThreads/4)控制啟動數(shù)量淆游,
每個線程每次只掃描一個分區(qū)Region
傍睹,從而標(biāo)記出存活對象圖。
所有的標(biāo)記任務(wù)必須在堆滿前就完成掃描稽犁,如果并發(fā)標(biāo)記耗時很長焰望,那么有可能在并發(fā)標(biāo)記過程中,又經(jīng)歷了幾次年輕代收集已亥。
如果堆滿前沒有完成標(biāo)記任務(wù)熊赖,則會觸發(fā)擔(dān)保機(jī)制,經(jīng)歷一次長時間的串行Full GC虑椎。
Remark
( 重新標(biāo)記震鹉,STW)
標(biāo)記那些在并發(fā)標(biāo)記階段發(fā)生變化的對象俱笛,將被回收。
這個階段也是并行執(zhí)行的传趾,通過參數(shù)-XX:ParallelGCThread
可設(shè)置GC暫停時可用的GC線程數(shù)迎膜。
Cleanup
(清理,STW)
清除階段主要執(zhí)行以下操作:
-
RSet
梳理浆兰,啟發(fā)式算法會根據(jù)活躍度和RSet
尺寸對分區(qū)定義不同等級磕仅,同時RSet
數(shù)理也有助于發(fā)現(xiàn)無用的引用。參數(shù)-XX:+PrintAdaptiveSizePolicy
可以開啟打印啟發(fā)式算法決策細(xì)節(jié)簸呈; - 整理堆分區(qū)榕订,為混合收集周期識別回收收益高(基于釋放空間和暫停目標(biāo))的老年代分區(qū)集合;
- 識別所有空閑分區(qū)蜕便,即發(fā)現(xiàn)無存活對象的分區(qū)劫恒。該分區(qū)可在清除階段直接回收,無需等待下次收集周期轿腺。
年輕代收集 Young Collection /混合收集周期 Mixed Collection Cycle
當(dāng)應(yīng)用運行開始時两嘴,堆內(nèi)存可用空間還比較大,只會在年輕代滿時族壳,觸發(fā)年輕代收集憔辫;
隨著老年代內(nèi)存增長,當(dāng)?shù)竭_(dá)IHOP閾值-XX:InitiatingHeapOccupancyPercent
(老年代占整堆比决侈,默認(rèn)45%)時螺垢,G1開始著手準(zhǔn)備收集老年代空間喧务。
首先經(jīng)歷并發(fā)標(biāo)記周期 Concurrent Marking Cycle
赖歌,識別出高收益的老年代分區(qū),前文已述功茴。
但隨后G1并不會馬上開始一次混合收集庐冯,而是讓應(yīng)用線程先運行一段時間,等待觸發(fā)一次年輕代收集坎穿。
在這次STW中展父,G1將保準(zhǔn)整理混合收集周期。接著再次讓應(yīng)用線程運行玲昧,當(dāng)接下來的幾次年輕代收集時栖茉,將會有老年代分區(qū)加入到CSet中,
即觸發(fā)混合收集孵延,這些連續(xù)多次的混合收集稱為混合收集周期(Mixed Collection Cycle
)吕漂。
年輕代收集 Young Collection,YGC
每次收集過程中,既有并行執(zhí)行的活動尘应,也有串行執(zhí)行的活動惶凝,但都可以是多線程的吼虎。
在并行執(zhí)行的任務(wù)中,如果某個任務(wù)過重苍鲜,會導(dǎo)致其他線程在等待某項任務(wù)的處理思灰,需要對這些地方進(jìn)行優(yōu)化。
以下部分部分可以結(jié)合日志查看
-
并行活動
外部根分區(qū)掃描 Ext Root Scanning:
此活動對堆外的根(JVM系統(tǒng)目錄混滔、VM數(shù)據(jù)結(jié)構(gòu)洒疚、JNI線程句柄、硬件寄存器坯屿、全局變量拳亿、線程對棧根)進(jìn)行掃描,發(fā)現(xiàn)那些沒有加入到暫停收集集合CSet中的對象愿伴。如果系統(tǒng)目錄(單根)擁有大量加載的類肺魁,最終可能其他并行活動結(jié)束后,該活動依然沒有結(jié)束而帶來的等待時間隔节。更新已記憶集合 Update RS:
并發(fā)優(yōu)化線程會對臟卡片的分區(qū)進(jìn)行掃描更新日志緩沖區(qū)來更新RSet鹅经,但只會處理全局緩沖列表。作為補充怎诫,所有被記錄但是還沒有被優(yōu)化線程處理的剩余緩沖區(qū)瘾晃,會在該階段處理,變成已處理緩沖區(qū)(Processed Buffers)幻妓。為了限制花在更新RSet的時間蹦误,可以設(shè)置暫停占用百分比-XX:G1RSetUpdatingPauseTimePercent
(默認(rèn)10%,即-XX:MaxGCPauseMills/10)肉津。值得注意的是强胰,如果更新日志緩沖區(qū)更新的任務(wù)不降低,單純地減少RSet的更新時間妹沙,會導(dǎo)致暫停中被處理的緩沖區(qū)減少偶洋,將日志緩沖區(qū)更新工作推到并發(fā)優(yōu)化線程上,從而增加對Java應(yīng)用線程資源的爭奪距糖。RSet掃描 Scan RS:
在收集當(dāng)前CSet之前玄窝,考慮到分區(qū)外的引用,必須掃描CSet分區(qū)的RSet悍引。如果RSet發(fā)生粗化恩脂,則會增加RSet的掃描時間。
開啟診斷模式-XX:UnlockDiagnosticVMOptions
后趣斤,
通過參數(shù)-XX:+G1SummarizeRSetStats
可以確定并發(fā)優(yōu)化線程是否能夠及時處理更新日志緩沖區(qū)俩块,并提供更多的信息,來幫助為RSet粗化總數(shù)提供窗口。
參數(shù)-XX:G1SummarizeRSetStatsPeriod=n
可設(shè)置RSet的統(tǒng)計周期典阵,即經(jīng)歷多少此GC后進(jìn)行一次統(tǒng)計代碼根掃描 Code Root Scanning:對代碼根集合進(jìn)行掃描奋渔,掃描JVM編譯后代碼Native Method的引用信息(nmethod掃描),進(jìn)行RSet掃描壮啊。事實上嫉鲸,只有CSet分區(qū)中的RSet有強(qiáng)代碼根時,才會做nmethod掃描歹啼,查找對CSet的引用玄渗。
轉(zhuǎn)移和回收 Object Copy:
通過選定的CSet以及CSet分區(qū)完整的引用集,將執(zhí)行暫停時間的主要部分:CSet分區(qū)存活對象的轉(zhuǎn)移狸眼、CSet分區(qū)空間的回收藤树。通過工作竊取機(jī)制來負(fù)載均衡地選定復(fù)制對象的線程,并且復(fù)制和掃描對象被轉(zhuǎn)移的存活對象將拷貝到每個GC線程分配緩沖區(qū)GCLAB拓萌。G1會通過計算岁钓,預(yù)測分區(qū)復(fù)制所花費的時間,從而調(diào)整年輕代的尺寸微王。終止 Termination:
完成上述任務(wù)后屡限,如果任務(wù)隊列已空,則工作線程會發(fā)起終止要求炕倘。如果還有其他線程繼續(xù)工作钧大,空閑的線程會通過工作竊取機(jī)制嘗試幫助其他線程處理。而單獨執(zhí)行根分區(qū)掃描的線程罩旋,如果任務(wù)過重啊央,最終會晚于終止。GC外部的并行活動 GC Worker Other:
該部分并非GC的活動涨醋,而是JVM的活動導(dǎo)致占用了GC暫停時間(例如JNI編譯)瓜饥。
-
串行活動
代碼根更新 Code Root Fixup:根據(jù)轉(zhuǎn)移對象更新代碼根。
代碼根清理 Code Root Purge:清理代碼根集合表东帅。
清除全局卡片標(biāo)記 Clear CT:在任意收集周期會掃描CSet與RSet記錄的PRT压固,掃描時會在全局卡片表中進(jìn)行標(biāo)記,防止重復(fù)掃描靠闭。在收集周期的最后將會清除全局卡片表中的已掃描標(biāo)志。
選擇下次收集集合 Choose CSet:該部分主要用于并發(fā)標(biāo)記周期后的年輕代收集坎炼、以及混合收集中愧膀,在這些收集過程中,由于有老年代候選分區(qū)的加入谣光,往往需要對下次收集的范圍做出界定檩淋;但單純的年輕代收集中,所有收集的分區(qū)都會被收集,不存在選擇蟀悦。
引用處理 Ref Proc:主要針對軟引用媚朦、弱引用、虛引用日戈、final引用询张、JNI引用。當(dāng)Ref Proc占用時間過多時浙炼,可選擇使用參數(shù)
-XX:ParallelRefProcEnabled
激活多線程引用處理份氧。G1希望應(yīng)用能小心使用軟引用,因為軟引用會一直占據(jù)內(nèi)存空間直到空間耗盡時被Full GC回收掉弯屈;即使未發(fā)生Full GC蜗帜,軟引用對內(nèi)存的占用,也會導(dǎo)致GC次數(shù)的增加资厉。引用排隊 Ref Enq:此項活動可能會導(dǎo)致RSet的更新厅缺,此時會通過記錄日志,將關(guān)聯(lián)的卡片標(biāo)記為臟卡片宴偿。
卡片重新臟化 Redirty Cards:重新臟化卡片店归。
回收空閑巨型分區(qū) Humongous Reclaim:G1做了一個優(yōu)化:通過查看所有根對象以及年輕代分區(qū)的RSet,如果確定RSet中巨型對象沒有任何引用酪我,則說明G1發(fā)現(xiàn)了一個不可達(dá)的巨型對象消痛,該對象分區(qū)會被回收。
釋放分區(qū) Free CSet:回收CSet分區(qū)的所有空間都哭,并加入到空閑分區(qū)中秩伞。
其他活動 Other:GC中可能還會經(jīng)歷其他耗時很小的活動,如修復(fù)JNI句柄等欺矫。
并發(fā)標(biāo)記周期后的年輕代收集 Young Collection Following Concurrent Marking Cycle
當(dāng)G1發(fā)起并發(fā)標(biāo)記周期之后纱新,并不會馬上開始混合收集。
G1會先等待下一次年輕代收集穆趴,然后在該收集階段中脸爱,確定下次混合收集的CSet(Choose CSet)。
混合收集周期 Mixed Collection Cycle, Mixed GC
單次的混合收集與年輕代收集并無二致未妹。
根據(jù)暫停目標(biāo)簿废,老年代的分區(qū)可能不能一次暫停收集中被處理完,G1會發(fā)起連續(xù)多次的混合收集络它,稱為混合收集周期(Mixed Collection Cycle)族檬。
G1會計算每次加入到CSet中的分區(qū)數(shù)量、混合收集進(jìn)行次數(shù)化戳,并且在上次的年輕代收集单料、以及接下來的混合收集中,G1會確定下次加入CSet的分區(qū)集(Choose CSet),并且確定是否結(jié)束混合收集周期扫尖。
轉(zhuǎn)移失敗的擔(dān)保機(jī)制 Full GC
轉(zhuǎn)移失敗(Evacuation Failure
)是指當(dāng)G1無法在堆空間中申請新的分區(qū)時白对,G1便會觸發(fā)擔(dān)保機(jī)制,執(zhí)行一次STW
式的换怖、單線程(JDK10支持多線程)的Full GC甩恼。
Full GC會對整堆做標(biāo)記清除和壓縮,最后將只包含純粹的存活對象狰域。參數(shù)-XX:G1ReservePercent
(默認(rèn)10%)可以保留空間媳拴,來應(yīng)對晉升模式下的異常情況,最大占用整堆50%兆览,更大也無意義屈溉。
G1在以下場景中會觸發(fā)Full GC,同時會在日志中記錄to-space exhausted
以及Evacuation Failure
:
- 從年輕代分區(qū)拷貝存活對象時抬探,無法找到可用的空閑分區(qū)
- 從老年代分區(qū)轉(zhuǎn)移存活對象時子巾,無法找到可用的空閑分區(qū)
- 分配巨型對象
Humongous Object
時在老年代無法找到足夠的連續(xù)分區(qū)
由于G1的應(yīng)用場合往往堆內(nèi)存都比較大,所以Full GC的收集代價非常昂貴小压,應(yīng)該避免Full GC的發(fā)生线梗。
問題
- 什么時候觸發(fā)concurrent marking ?
# 啟動并發(fā)周期 Concurrent Marking Cycle (以及后續(xù)的混合周期 MixedGC)時的堆內(nèi)存占用百分比. G1用它來觸發(fā)并發(fā)GC周期,基于整個堆的使用率,而不只是某一代內(nèi)存的使用比例怠益。默認(rèn)45%
# 當(dāng)堆存活對象占用堆的45%仪搔,就會啟動G1 中并發(fā)標(biāo)記周期 Concurrent Marking Cycle
-XX:InitiatingHeapOccupancyPercent
-
什么時候發(fā)生Mixed GC?
concurrent marking 主要是為Mixed GC提供標(biāo)記服務(wù)的,并不是一次GC過程的一個必須環(huán)節(jié)蜻牢。由一些參數(shù)控制烤咧,另外也控制著哪些老年代Region會被選入CSet(收集集合)。
# 一次 concurrent marking之后抢呆,最多執(zhí)行Mixed GC的次數(shù)(默認(rèn)8)
-XX:G1MixedGCCountTarget
# 堆廢物百分比(默認(rèn)5%)煮嫌,在每次YGC之后和再次發(fā)生Mixed GC之前,會檢查垃圾占比是否達(dá)到此參數(shù)抱虐,只有達(dá)到了昌阿,下次才會發(fā)生Mixed GC。
-XX:G1HeapWastePercent
# old generation region中的存活對象的占比恳邀,只有在此參數(shù)之下懦冰,才會被選入CSet。
-XX:G1MixedGCLiveThresholdPercent
# 一次Mixed GC中能被選入CSet的最多old generation region數(shù)量轩娶。
-XX:G1OldCSetRegionThresholdPercent
GC日志詳解
并發(fā)標(biāo)記周期 Concurrent Marking Cycle
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0094252 secs]
# 根分區(qū)掃描儿奶,可能會被 YGC 打斷,那么結(jié)束就是如:[GC pause (G1 Evacuation Pause) (young)[GC concurrent-root-region-scan-end, 0.0007157 secs]
[GC concurrent-mark-start]
[GC concurrent-mark-end, 0.0203881 secs]
# 并發(fā)標(biāo)記階段
[GC remark [Finalize Marking, 0.0007822 secs] [GC ref-proc, 0.0005279 secs] [Unloading, 0.0013783 secs], 0.0036513 secs]
# 重新標(biāo)記鳄抒,STW
[Times: user=0.01 sys=0.00, real=0.00 secs]
[GC cleanup 13985K->13985K(20480K), 0.0034675 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
# 清除
年輕代收集 YGC
[GC pause (G1 Evacuation Pause) (young), 0.0022483 secs]
# young -> 年輕代 Evacuation-> 復(fù)制存活對象
[Parallel Time: 1.0 ms, GC Workers: 10] # 并發(fā)執(zhí)行的GC線程數(shù),以下階段是并發(fā)執(zhí)行的
[GC Worker Start (ms): Min: 109.0, Avg: 109.1, Max: 109.1, Diff: 0.2]
[Ext Root Scanning (ms): Min: 0.1, Avg: 0.2, Max: 0.3, Diff: 0.2, Sum: 2.3] # 外部根分區(qū)掃描
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] # 更新已記憶集合 Update RSet,檢測從年輕代指向老年代的對象
[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]# RSet掃描
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1] # 代碼根掃描
[Object Copy (ms): Min: 0.3, Avg: 0.3, Max: 0.4, Diff: 0.1, Sum: 3.5] # 轉(zhuǎn)移和回收许溅,拷貝存活的對象到survivor/old區(qū)域
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] # 完成上述任務(wù)后瓤鼻,如果任務(wù)隊列已空,則工作線程會發(fā)起終止要求贤重。
[Termination Attempts: Min: 1, Avg: 5.8, Max: 9, Diff: 8, Sum: 58]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1] # GC外部的并行活動茬祷,該部分并非GC的活動,而是JVM的活動導(dǎo)致占用了GC暫停時間(例如JNI編譯)并蝗。
[GC Worker Total (ms): Min: 0.5, Avg: 0.6, Max: 0.7, Diff: 0.2, Sum: 5.9]
[GC Worker End (ms): Min: 109.7, Avg: 109.7, Max: 109.7, Diff: 0.0]
[Code Root Fixup: 0.0 ms] # 串行任務(wù)祭犯,根據(jù)轉(zhuǎn)移對象更新代碼根
[Code Root Purge: 0.0 ms] #串行任務(wù), 代碼根清理
[Clear CT: 0.5 ms] #串行任務(wù)滚停,清除全局卡片 Card Table 標(biāo)記
[Other: 0.8 ms]
[Choose CSet: 0.0 ms] # 選擇下次收集集合 CSet
[Ref Proc: 0.4 ms] # 引用處理 Ref Proc沃粗,處理軟引用、弱引用键畴、虛引用最盅、final引用、JNI引用
[Ref Enq: 0.0 ms] # 引用排隊 Ref Enq
[Redirty Cards: 0.3 ms] # 卡片重新臟化 Redirty Cards:重新臟化卡片
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms] # 回收空閑巨型分區(qū) Humongous Reclaim起惕,通過查看所有根對象以及年輕代分區(qū)的RSet涡贱,如果確定RSet中巨型對象沒有任何引用,該對象分區(qū)會被回收惹想。
[Free CSet: 0.0 ms] # 釋放分區(qū) Free CSet
[Eden: 12288.0K(12288.0K)->0.0B(11264.0K) Survivors: 0.0B->1024.0K Heap: 12288.0K(20480.0K)->832.0K(20480.0K)]
[Times: user=0.01 sys=0.00, real=0.00 secs]
# 從年輕代分區(qū)拷貝存活對象時问词,無法找到可用的空閑分區(qū)
# 從老年代分區(qū)轉(zhuǎn)移存活對象時,無法找到可用的空閑分區(qū) 這兩種情況之一導(dǎo)致的 YGC
[GC pause (G1 Evacuation Pause) (young) (to-space exhausted), 0.0916534 secs]
# 并發(fā)標(biāo)記周期 Concurrent Marking Cycle 中的 根分區(qū)掃描階段嘀粱,被 YGC中斷
[GC pause (G1 Evacuation Pause) (young)[GC concurrent-root-region-scan-end, 0.0007157 secs]
混合收集周期 Mixed Collection Cycle, Mixed GC
# 并發(fā)標(biāo)記周期 Concurrent Marking Cycle 的開始
[GC pause (G1 Evacuation Pause) (young) (initial-mark) , 0.0443460 secs]
Full GC
[Full GC (Allocation Failure) 20480K->9656K(20480K), 0.0189481 secs]
[Eden: 0.0B(1024.0K)->0.0B(5120.0K) Survivors: 0.0B->0.0B Heap: 20480.0K(20480.0K)->9656.8K(20480.0K)], [Metaspace: 4960K->4954K(1056768K)]
[Times: user=0.03 sys=0.00, real=0.02 secs]
參考資料
- https://www.oracle.com/technical-resources/articles/java/g1gc.html
- JDK8 G1: https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html
- Other Blog:
- https://www.infoq.com/articles/G1-One-Garbage-Collector-To-Rule-Them-All/
- https://tech.meituan.com/2016/09/23/g1.html
- https://www.infoq.com/articles/tuning-tips-G1-GC/
- https://blog.csdn.net/coderlius/article/details/79272773
- https://www.cnblogs.com/webor2006/p/11146273.html
- https://www.cnblogs.com/webor2006/p/11147545.html
- [1] Charlie H, Monica B, Poonam P, Bengt R. Java Performance Companion
- [2] 周志明. 深入理解JVM虛擬機(jī)
by Sven Augustus https://my.oschina.net/langxSpirit
本文由博客群發(fā)一文多發(fā)等運營工具平臺 OpenWrite 發(fā)布