1. CMS
CMS過程在上篇文章 GC垃圾回收(2) 中已經(jīng)寫過。
它分為四個階段:
1 initial mark (初始標(biāo)記)
2 concurrent mark (并發(fā)標(biāo)記)
3 remark (重新標(biāo)記)
4 concurrent sweep (并發(fā)清理)
其中 并發(fā)標(biāo)記 階段會有漏標(biāo)的問題,為解決這個問題,采用了 "三色標(biāo)記算法"
2. G1
G1 GC(Garbage First Garbage Collector)是一種服務(wù)端應(yīng)用使用的垃圾收集器,目標(biāo)是用在 多核、大內(nèi)存的機(jī)器上,它在大多數(shù)情況下可以實(shí)現(xiàn)指定的GC暫停時(shí)間,同時(shí)還能保持較高的吞吐量。它的吞吐量相較PS+PO降低了大概10%~15%,但是大大降低了響應(yīng)時(shí)間,大概200ms的程度
2.1 G1內(nèi)存模型
G1內(nèi)存模型如下:

G1相較之前其它的垃圾回收器,對模型進(jìn)行了改變,不再進(jìn)行物理分代,采用邏輯分代。
它不再將連續(xù)內(nèi)存分為Eden區(qū)和Old區(qū),而是將內(nèi)存分為一個個的Region。一塊Region(分區(qū))在邏輯上依然分代,分為四種:Eden,Old,Survivor,Humongous(大對象,跨多個連續(xù)的Region)。
它的每個分區(qū)都可能是年輕代也可能是老年代,但是在同一時(shí)刻只能屬于某個代。
年輕代、幸存區(qū)、老年代這些概念還存在,成為了邏輯上的概念,這樣方便復(fù)用之前分代框架的邏輯。在物理上不需要連續(xù),這帶來了額外的好處——有的分區(qū)內(nèi)垃圾對象特別多,有的分區(qū)內(nèi)垃圾對象很少,G1會優(yōu)先回收垃圾對象特別多的分區(qū),這樣可以花費(fèi)較少的時(shí)間來回收這些分區(qū)的垃圾,這也就是G1名字的由來,即首先回收垃圾最多的分區(qū)。
新生代其實(shí)并不適用于這種算法,依然是在新生代滿了的時(shí)候,對整個新生代進(jìn)行回收——整個新生代中的對象,要么被回收、要么晉升,至于新生代也采取分區(qū)機(jī)制的原因,則是因?yàn)檫@樣跟老年代的策略統(tǒng)一,方便調(diào)整代的大小。
G1還是一種帶壓縮的收集器,在回收老年代的分區(qū)時(shí),是將存活的對象從一個分區(qū)拷貝到另一個可用分區(qū),這個拷貝的過程就實(shí)現(xiàn)了局部的壓縮。每個分區(qū)的大小從1M到32M不等,但都是2的冪次方。
特點(diǎn):
- 并發(fā)收集
- 壓縮空閑時(shí)間不會延長GC的暫停時(shí)間
- 更易預(yù)測GC的暫停時(shí)間
- 適用不需要實(shí)現(xiàn)很高吞吐量的場景
G1與CMS在并發(fā)收集時(shí)的算法沒太大區(qū)別,用的是 三色標(biāo)記 算法。但ZGC和Shenandoah使用的是 顏色指針 Colored Pointers。
2.2 基本概念
2.2.1 CSet
- CSet = Collection set
- 一組可被回收的分區(qū)的集合。
在ZSet中存活的數(shù)據(jù)會在GC過程中被移動到另一個可用分區(qū),CSet中的分區(qū)可以來自Eden空間、survivor空間、或者老年代。CSet會占用不到整個堆空間的1%大小。
2.2.2 Card Table
主要用于分代模型中幫助垃圾回收。
為什么需要 card table ?
尋找存活對象并不是一件容易的事。從一個GC root對象尋找,可能被Old區(qū)對象引用,這個Old區(qū)對象又被Eden區(qū)對象引用,那么判斷Eden區(qū)對象是否存活就需要遍歷整個Old區(qū)存活對象看是否被Old區(qū)對象引用。這樣的話每進(jìn)行一次YGC就要掃描整個Old區(qū)。
所以JVM內(nèi)部,將內(nèi)存區(qū)域分為一個個的card,對象存在一個個的card里。當(dāng)老年代某個card中的對象指向了年輕代,就會將這個card標(biāo)記為 Dirty 。這么多card具體哪個是 Dirty的,用位圖BitMap來代表(如0110010010,1表示Dirty),這就是Card Table。
Card Table :由于做YGC時(shí),需要掃描整個Old區(qū),效率非常低,所以JVM設(shè)計(jì)了Card Table, 如果一個Old區(qū)Card Table中有對象指向Y區(qū),就將它設(shè)為Dirty,下次掃描時(shí),只需要掃描Dirty Card。 在結(jié)構(gòu)上,Card Table用BitMap來實(shí)現(xiàn)。
2.2.3 RSet
- RSet = Remembered Set
- 記錄了其他Region中的對象到本Region的引用
RSet的價(jià)值在于:使得垃圾收集器不需要掃描整個堆,找到誰引用了當(dāng)前分區(qū)中的對象,只需要掃描RSet即可。
RSet會占用一定的空間,所以ZGC又做了改進(jìn),不使用RSet,用顏色指針來標(biāo)記。
Rset與賦值的效率:
- 由于RSet的存在,那么每次給對象賦引用的時(shí)候,就要做一些額外的操作
- 額外的操作,指的是在RSet中做一些額外的記錄(在GC中被稱為寫屏障)
- 這個寫屏障 不是 內(nèi)存屏障
2.2.4 新老年代比例
5% ~ 60%(新生代)
- 一般不用手動指定
- 也不要手動指定,因?yàn)檫@是G1預(yù)測停頓時(shí)間的基準(zhǔn)
G1能跟蹤STW停頓時(shí)間,根據(jù)停頓時(shí)間動態(tài)調(diào)整新生代(Y區(qū))比例
2.2.5 humongous object
超過單個region的 50% 就是一個大對象,也可跨越多個region。
2.3 GC何時(shí)觸發(fā)
- YGC
- Eden區(qū)空間不足
- 多線程并行執(zhí)行
- FGC
- Old空間不足
- System.gc()
注意:G1也是存在FGC的,并且一定會被觸發(fā)。當(dāng)對象分配不下是會產(chǎn)生FGC。
2.3.1 如果G1產(chǎn)生FGC,應(yīng)該做什么,如何優(yōu)化?
- 擴(kuò)內(nèi)存
- 提高CPU性能(回收的快,業(yè)務(wù)邏輯產(chǎn)生對象的速度固定,垃圾回收越快,內(nèi)存空間越大)
- (主要)降低MixedGC觸發(fā)的閾值,讓MixedGC提早發(fā)生(默認(rèn)是45%)
2.3.2 G1中的MixedGC
- 相當(dāng)于CMS
- XX:InitiatingHeapOccupacyPercent
--默認(rèn)值45%
--當(dāng)堆內(nèi)存超過這個值,觸發(fā)MixedGC
回收時(shí)不分新生代還是老年代什么的,region滿了就回收。
MixedGC過程:
- 初始標(biāo)記 STW
- 并發(fā)標(biāo)記
- 最終標(biāo)記 STW(重新標(biāo)記)
- 篩選回收 STW(并行)
跟CMS非常像,MixedGC最后是篩選回收,多了個篩選步驟。篩選就是找出垃圾最多的region。篩選后將存活對象復(fù)制到其他region,再將之前的region清空。
2.4 并發(fā)標(biāo)記算法
CMS和G1在并發(fā)標(biāo)記時(shí)使用的是同一個算法:三色標(biāo)記法,使用白灰黑三種顏色標(biāo)記對象。白色是未標(biāo)記;灰色自身被標(biāo)記,引用的對象未標(biāo)記;黑色自身與引用對象都已標(biāo)記。

2.4.1 漏標(biāo)問題
在remark過程中,黑色指向了白色,如果不對黑色重新掃描,則會漏標(biāo)。會把白色D對象當(dāng)作沒有新引用指向從而回收掉。

并發(fā)標(biāo)記過程中,Mutator刪除了所有從灰色到白色的引用,會產(chǎn)生漏標(biāo)。此時(shí)白色對象應(yīng)該被回收
產(chǎn)生漏標(biāo)問題的條件有兩個:
1.黑色對象指向了白色對象
2.灰色對象指向白色對象的引用消失
所以要解決漏標(biāo)問題,打破兩個條件之一即可:
- 跟蹤黑指向白的增加
incremental update:增量更新,關(guān)注引用的增加,把黑色重新標(biāo)記為灰色,下次重新掃描屬性。CMS采用該方法。 - 記錄灰指向白的消失
SATB snapshot at the beginning:關(guān)注引用的刪除,當(dāng)灰-->白消失時(shí),要把這個 引用 推到GC的堆棧,保證白還能被GC掃描到。G1采用該方法。
為什么G1采用SATB而不用incremental update?
因?yàn)椴捎胕ncremental update把黑色重新標(biāo)記為灰色后,之前掃描過的還要再掃描一遍,效率太低。
G1有RSet與SATB相配合。Card Table里記錄了RSet,RSet里記錄了其他對象指向自己的引用,這樣就不需要再掃描其他區(qū)域,只要掃描RSet就可以了。
也就是說 灰色-->白色 引用消失時(shí),如果沒有 黑色-->白色,引用會被push到堆棧,下次掃描時(shí)拿到這個引用,由于有RSet的存在,不需要掃描整個堆去查找指向白色的引用,效率比較高。SATB配合RSet渾然天成。