java垃圾收集器-CMS G1 ZGC

CMS

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。這是因?yàn)镃MS收集器工作時(shí),GC工作線程與用戶線程可以并發(fā)執(zhí)行,以此來達(dá)到降低收集停頓時(shí)間的目的。
CMS收集器僅作用于老年代的收集,是基于標(biāo)記-清除算法的,它的運(yùn)作過程分為4個(gè)步驟:
初始標(biāo)記(CMS initial mark)
并發(fā)標(biāo)記(CMS concurrent mark)
重新標(biāo)記(CMS remark)
并發(fā)清除(CMS concurrent sweep)
其中,初始標(biāo)記、重新標(biāo)記這兩個(gè)步驟仍然需要Stop-the-world。初始標(biāo)記僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快,并發(fā)標(biāo)記階段就是進(jìn)行GC Roots Tracing的過程,而重新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,這個(gè)階段的停頓時(shí)間一般會(huì)比初始階段稍長(zhǎng)一些,但遠(yuǎn)比并發(fā)標(biāo)記的時(shí)間短
具體參考:http://www.lxweimin.com/p/2a1b2f17d3e4
CMS收集器優(yōu)點(diǎn):并發(fā)收集、低停頓。
CMS收集器缺點(diǎn):
CMS收集器對(duì)CPU資源非常敏感。
CMS收集器無法處理浮動(dòng)垃圾(Floating Garbage)。因?yàn)橹匦聵?biāo)記之后,并行清除的時(shí)候用戶線程和gc線程同時(shí)運(yùn)行,還會(huì)導(dǎo)致一部分對(duì)象不可達(dá)但是不會(huì)被回收
CMS收集器是基于標(biāo)記-清除算法,該算法的缺點(diǎn)都有。
CMS收集器之所以能夠做到并發(fā),根本原因在于采用基于“標(biāo)記-清除”的算法并對(duì)算法過程進(jìn)行了細(xì)粒度的分解。前面篇章介紹過標(biāo)記-清除算法將產(chǎn)生大量的內(nèi)存碎片這對(duì)新生代來說是難以接受的,因此新生代的收集器并未提供CMS版本。

安全點(diǎn)

用戶程序執(zhí)行時(shí)并非在代碼指令流的任意位置都能夠停頓下來開始垃圾收集,而是強(qiáng)制要求必須執(zhí)行到達(dá)安全點(diǎn)后才能夠暫停。
安全點(diǎn)的選定既不能太少以至于讓收集器等待時(shí)間過長(zhǎng),也不能太過頻繁以至于過分增大運(yùn)行時(shí)的內(nèi)存負(fù)荷。
安全點(diǎn)位置的選取基本上是以“是否具有讓程序長(zhǎng)時(shí)間執(zhí)行的特征”為標(biāo)準(zhǔn) 進(jìn)行選定的
“長(zhǎng)時(shí)間執(zhí)行”的最明顯特征就是指令序列的復(fù)用,例如方法調(diào)用、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn) 等都屬于指令序列復(fù)用,所以只有具有這些功能的指令才會(huì)產(chǎn)生安全點(diǎn)。

G1

G1重新定義了堆空間,打破了原有的分代模型,將堆劃分為一個(gè)個(gè)區(qū)域。這么做的目的是在進(jìn)行收集時(shí)不必在全堆范圍內(nèi)進(jìn)行,這是它最顯著的特點(diǎn)。區(qū)域劃分的好處就是帶來了停頓時(shí)間可預(yù)測(cè)的收集模型:用戶可以指定收集操作在多長(zhǎng)時(shí)間內(nèi)完成。即G1提供了接近實(shí)時(shí)的收集特性

1.young gc

G1的新生代收集跟ParNew類似,當(dāng)新生代占用達(dá)到一定比例的時(shí)候,開始出發(fā)收集。全過程stw,會(huì)根據(jù)預(yù)期停頓時(shí)間決定回收多少region(其實(shí)是eden區(qū)占用多少的region,會(huì)回收所有eden區(qū)的region)。筆者服務(wù)堆12g,eden區(qū)分配了8G,young gc耗時(shí)15ms。

2.mixed gc

選定所有年輕代里的Region,外加根據(jù)global concurrent marking統(tǒng)計(jì)得出收集收益高的若干老年代Region。在用戶指定的開銷目標(biāo)范圍內(nèi)盡可能選擇收益高的老年代Region。

初始標(biāo)記

此階段需要stop the world。這一階段會(huì)觸發(fā)新生代垃圾回收。標(biāo)記可能引用了老年代對(duì)象的survivor區(qū)域。在日志中以 GC pause (young)(inital-mark)標(biāo)識(shí)。

根區(qū)域掃描

掃描引用了老年代的survivor區(qū)域。此階段和用戶線程并發(fā)執(zhí)行。并且只有完成該階段后,才能開始下一次 STW 年輕代垃圾回收。(可以理解為初始標(biāo)記的延續(xù),不過是并發(fā)執(zhí)行的)

并發(fā)標(biāo)記

在整個(gè)堆中進(jìn)行并發(fā)標(biāo)記(和應(yīng)用程序并發(fā)執(zhí)行),此過程可能被young GC中斷。在并發(fā)標(biāo)記階段,若發(fā)現(xiàn)區(qū)域?qū)ο笾械乃袑?duì)象都是垃圾,那個(gè)這個(gè)區(qū)域會(huì)在Remark階段被立即回收(圖中打X)。同時(shí),并發(fā)標(biāo)記過程中,會(huì)計(jì)算每個(gè)區(qū)域的對(duì)象活性(區(qū)域中存活對(duì)象的比例)。

重標(biāo)記

會(huì)有短暫停頓(STW)。再標(biāo)記階段是用來收集 并發(fā)標(biāo)記階段 產(chǎn)生新的垃圾(并發(fā)階段和應(yīng)用程序一同運(yùn)行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。

Copy/Clean up

多線程清除失活對(duì)象,G1選擇“活躍度(liveness)”最低的區(qū)域, 這些區(qū)域可以最快的完成回收,會(huì)有STW。
G1將回收區(qū)域的存活對(duì)象拷貝到新區(qū)域,清除Remember Sets

大對(duì)象處理

在G1中,對(duì)于超過0.5個(gè)region size的對(duì)象是大對(duì)象,對(duì)于大對(duì)象不會(huì)移動(dòng),只會(huì)清理,并且直接分配到old中。

ZGC

image.png

具體參考:https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html
https://juejin.cn/post/6844903957492563975
https://blog.csdn.net/fedorafrog/article/details/113782570

Remembered Set

在CMS中,也有RSet的概念,在老年代中有一塊區(qū)域用來記錄指向新生代的引用。這是一種point-out,在進(jìn)行Young GC時(shí),掃描根時(shí),僅僅需要掃描這一塊區(qū)域,而不需要掃描整個(gè)老年代。
但在G1中,并沒有使用point-out,這是由于一個(gè)分區(qū)太小,分區(qū)數(shù)量太多,如果是用point-out的話,會(huì)造成大量的掃描浪費(fèi),有些根本不需要GC的分區(qū)引用也掃描了。于是G1中使用point-in來解決。point-in的意思是哪些分區(qū)引用了當(dāng)前分區(qū)中的對(duì)象。這樣,僅僅將這些對(duì)象當(dāng)做根來掃描就避免了無效的掃描。由于新生代有多個(gè),那么我們需要在新生代之間記錄引用嗎?這是不必要的,原因在于每次GC時(shí),所有新生代都會(huì)被掃描,所以只需要記錄老年代到新生代之間的引用即可。
就內(nèi)存占用而言,雖然G1和CMS都使用卡表來處理跨代指針,但G1的卡表實(shí)現(xiàn)更為復(fù)雜,而且堆中每個(gè)Region,無論扮演的是新生代還是老年代角色,都必須有一份卡表,這導(dǎo)致G1的記憶集(和其他內(nèi)存消耗 )可能會(huì)占生個(gè)堆容量的20%乃至更多;相比起來CMS的卡表就相當(dāng)簡(jiǎn)單,只有唯一一份,而且只需要處理老年代到新生代的引用,反過來則不需要,由于新生代的對(duì)象具有朝生夕滅的不穩(wěn)定性,引用變化頻繁,能省下這個(gè)區(qū)域的維護(hù)開銷是很劃算的(代價(jià)就是當(dāng)CMS發(fā)生Major GC時(shí),要把整個(gè)新生代作為GC Roots來進(jìn)行掃描)。
更詳細(xì)信息:https://blog.csdn.net/z69183787/article/details/108558963
http://www.lxweimin.com/p/8d37a07277e0

G1 參數(shù)調(diào)優(yōu)

-XX:MaxGCPauseMillis

暫停時(shí)間,默認(rèn)值200ms。這是一個(gè)軟性目標(biāo),G1會(huì)盡量達(dá)成,如果達(dá)不成,會(huì)逐漸做自我調(diào)整。
對(duì)于Young GC來說,會(huì)逐漸減少Eden區(qū)個(gè)數(shù),減少Eden空間那么Young GC的處理時(shí)間就會(huì)相應(yīng)減少。對(duì)于Mixed GC,G1會(huì)調(diào)整每次Choose Cset的比例,默認(rèn)最大值是10%,當(dāng)然每次選擇的Cset少了,所要經(jīng)歷的Mixed GC的次數(shù)會(huì)相應(yīng)增加。
減少Eden的總空間時(shí),就會(huì)更加頻繁的觸發(fā)Young GC,也就是會(huì)加快Mixed GC的執(zhí)行頻率,因?yàn)镸ixed GC是由Young GC觸發(fā)的,或者說借機(jī)同時(shí)執(zhí)行的。頻繁GC會(huì)對(duì)對(duì)應(yīng)用的吞吐量造成影響,每次Mixed GC回收時(shí)間太短,回收的垃圾量太少,可能最后GC的垃圾清理速度趕不上應(yīng)用產(chǎn)生的速度,那么可能會(huì)造成串行的Full GC,這是要極力避免的。所以暫停時(shí)間肯定不是設(shè)置的越小越好,當(dāng)然也不能設(shè)置的偏大,轉(zhuǎn)而指望G1自己會(huì)盡快的處理,這樣可能會(huì)導(dǎo)致一次全部并發(fā)標(biāo)記后觸發(fā)的Mixed GC次數(shù)變少,但每次的時(shí)間變長(zhǎng),STW時(shí)間變長(zhǎng),對(duì)應(yīng)用的影響更加明顯

-XX:G1NewSizePercent和-XX:G1MaxNewSizePercent

新生代比例有兩個(gè)數(shù)值指定,下限:-XX:G1NewSizePercent,默認(rèn)值5%,上限:-XX:G1MaxNewSizePercent,默認(rèn)值60%。G1會(huì)根據(jù)實(shí)際的GC情況(主要是暫停時(shí)間)來動(dòng)態(tài)的調(diào)整新生代的大小,主要是Eden Region的個(gè)數(shù)。最好是Eden的空間大一點(diǎn),畢竟Young GC的頻率更大,大的Eden空間能夠降低Young GC的發(fā)生次數(shù)。但是Mixed GC是伴隨著Young GC一起的,如果暫停時(shí)間短,那么需要更加頻繁的Young GC,同時(shí)也需要平衡好Mixed GC中新生代和老年代的Region,因?yàn)樾律乃蠷egion都會(huì)被回收,如果Eden很大,那么留給老年代回收空間就不多了,最后可能會(huì)導(dǎo)致Full GC。

-XX:G1MixedGCLiveThresholdPercent

通過-XX:G1MixedGCLiveThresholdPercent指定被納入Cset的Region的存活空間占比閾值,不同版本默認(rèn)值不同,有65%和85%。在全局并發(fā)標(biāo)記階段,如果一個(gè)Region的存活對(duì)象的空間占比低于此值,則會(huì)被納入Cset。此值直接影響到Mixed GC選擇回收的區(qū)域,當(dāng)發(fā)現(xiàn)GC時(shí)間較長(zhǎng)時(shí),可以嘗試調(diào)低此閾值,盡量?jī)?yōu)先選擇回收垃圾占比高的Region,但此舉也可能導(dǎo)致垃圾回收的不夠徹底,最終觸發(fā)Full GC。

-XX:InitiatingHeapOccupancyPercent

通過-XX:InitiatingHeapOccupancyPercent指定觸發(fā)全局并發(fā)標(biāo)記的老年代使用占比,默認(rèn)值45%,也就是老年代占堆的比例超過45%。如果Mixed GC周期結(jié)束后老年代使用率還是超過45%,那么會(huì)再次觸發(fā)全局并發(fā)標(biāo)記過程,這樣就會(huì)導(dǎo)致頻繁的老年代GC,影響應(yīng)用吞吐量。同時(shí)老年代空間不大,Mixed GC回收的空間肯定是偏少的。可以適當(dāng)調(diào)高IHOP的值,當(dāng)然如果此值太高,很容易導(dǎo)致年輕代晉升失敗而觸發(fā)Full GC,所以需要多次調(diào)整測(cè)試。

空的區(qū)域被移除并回收。并計(jì)算所有區(qū)域的活躍度(Region liveness).
G1具備如下特點(diǎn):
并行與并發(fā):G1能充分利用多CPU、多核環(huán)境下的硬件優(yōu)勢(shì),使用多個(gè)CPU來縮短Stop-the-world停頓的時(shí)間,部分其他收集器原來需要停頓Java線程執(zhí)行的GC操作,G1收集器仍然可以通過并發(fā)的方式讓Java程序繼續(xù)運(yùn)行。
分代收集
空間整合:與CMS的標(biāo)記-清除算法不同,G1從整體來看是基于標(biāo)記-整理算法實(shí)現(xiàn)的收集器,從局部(兩個(gè)Region之間)上來看是基于“復(fù)制”算法實(shí)現(xiàn)的。但無論如何,這兩種算法都意味著G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片,收集后能提供規(guī)整的可用內(nèi)存。這種特性有利于程序長(zhǎng)時(shí)間運(yùn)行,分配大對(duì)象時(shí)不會(huì)因?yàn)闊o法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次GC。
可預(yù)測(cè)的停頓:這是G1相對(duì)于CMS的一個(gè)優(yōu)勢(shì),降低停頓時(shí)間是G1和CMS共同的關(guān)注點(diǎn)。
G1收集器之所以能建立可預(yù)測(cè)的停頓時(shí)間模型,是因?yàn)樗梢杂杏?jì)劃地避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集。G1會(huì)通過一個(gè)合理的計(jì)算模型,計(jì)算出每個(gè)Region的收集成本并量化,這樣一來,收集器在給定了“停頓”時(shí)間限制的情況下,總是能選擇一組恰當(dāng)?shù)腞egions作為收集目標(biāo),讓其收集開銷滿足這個(gè)限制條件,以此達(dá)到實(shí)時(shí)收集的目的。

G1與CMS對(duì)比

G1在壓縮空間方面有優(yōu)勢(shì)。
對(duì)于CMS是采用標(biāo)記清除算法,不帶壓縮功能的,所以肯定會(huì)有內(nèi)存碎片的產(chǎn)生,而G1采用的是拷貝復(fù)制算法,所以肯定不會(huì)產(chǎn)生內(nèi)存碎片問題,所以壓縮起來空間利用率也大大提升了。
G1通過將內(nèi)存空間分成區(qū)域(Region)的方式避免內(nèi)存碎片問題。
Eden、Survivor、Old區(qū)不再固定,在內(nèi)存使用率上來說更靈活。
因?yàn)镚1的Eden、Survivor、Old的個(gè)數(shù)是可以動(dòng)態(tài)伸縮的,而不像CMS,這些區(qū)域就已經(jīng)定好大小了不能被調(diào)整。
G1可以通過設(shè)置預(yù)期停頓時(shí)間(Pause Time)來控制垃圾收集時(shí)間,避免應(yīng)用雪崩現(xiàn)象。
G1在回收內(nèi)存后會(huì)馬上同時(shí)做合并空閑內(nèi)存的工作,而CMS默認(rèn)是在STW(stop the world)的時(shí)候做。
G1會(huì)在Young GC中使用,而CMS只能在Old區(qū)使用。
在執(zhí)行負(fù)載的角度上,同樣由于兩個(gè)收集器格子的細(xì)節(jié)實(shí)現(xiàn)特點(diǎn)導(dǎo)致了用戶程序運(yùn)行時(shí)的負(fù)載不同。譬如二者都使用到了寫屏障,CMS用寫后屏障來更新維護(hù)卡表,而G1除了使用寫后屏蔽來進(jìn)行同樣的卡表維護(hù)外,為了實(shí)現(xiàn)原始快照搜索,還需要使用寫前屏障來跟蹤并發(fā)時(shí)的指針變化情況。相比起增量更新算法,原始快照搜索算法能夠減少并發(fā)標(biāo)記和重新標(biāo)記階段的消耗,避免CMS那樣在最終標(biāo)記階段停頓時(shí)間過長(zhǎng)的缺點(diǎn),但是在用戶程序運(yùn)行過程中確實(shí)會(huì)產(chǎn)生由跟蹤引用變化帶來的額外負(fù)擔(dān)。由于G1對(duì)寫屏障的復(fù)雜操作要比CMS消耗更多的運(yùn)算資源,所以CMS的寫屏障實(shí)現(xiàn)是直接的同步操作,而G1就不得不將其實(shí)現(xiàn)為類似于消息隊(duì)列的結(jié)構(gòu),把寫前屏障和寫后屏障中要做的事情都放到隊(duì)列里,然后再異步處理。

對(duì)比

image.png

堆外內(nèi)存跟蹤

image.png

CMS G1 ZGC對(duì)比

image.png

image.png

參考:
http://www.lxweimin.com/p/2a1b2f17d3e4
https://blog.csdn.net/liwenshui322/article/details/88866564?spm=1001.2014.3001.5501
https://blog.csdn.net/z69183787/article/details/108558963
https://zhuanlan.zhihu.com/p/181305087
G1日志:https://github.com/bingoogolapple/bingoogolapple.github.io/issues/186
G1:https://segmentfault.com/a/1190000039411521

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 判斷對(duì)象已死 可達(dá)性分析算法 以一系列“GC Roots”對(duì)象作為根起點(diǎn),根據(jù)引用關(guān)系向下搜索,搜索過程所走過的路...
    x末影人x閱讀 449評(píng)論 0 0
  • 本文簡(jiǎn)單介紹了垃圾收集的幾種常見式,重點(diǎn)說明了G1回收的原理(畢竟JDK1.9 G1會(huì)是默認(rèn)的GC回收器–-...
    baker_dai閱讀 1,223評(píng)論 0 1
  • 本文首先簡(jiǎn)單介紹了垃圾收集的常見方式,然后再分析了G1收集器的收集原理,相比其他垃圾收集器的優(yōu)勢(shì),最后給出了一些調(diào)...
    秦漢郵俠閱讀 1,583評(píng)論 0 20
  • 本文首先簡(jiǎn)單介紹了垃圾收集的常見方式,然后再分析了G1收集器的收集原理,相比其他垃圾收集器的優(yōu)勢(shì),最后給出了一些調(diào)...
    梵小星閱讀 1,285評(píng)論 0 6
  • 從方法論上講,程序語(yǔ)言的回收算法主要分為 一、引用計(jì)數(shù)算法(Reference Counting):給對(duì)象添加一個(gè)...
    程序猿TODO閱讀 542評(píng)論 0 0