CMS垃圾收集器

CMS是老年代垃圾收集器顿天,在收集過程中可以與用戶線程并發(fā)操作橄务。它可以與Serial收集器和Parallel New收集器搭配使用误债。CMS犧牲了系統(tǒng)的吞吐量來追求收集速度票编,適合追求垃圾收集速度的服務(wù)器上◇凹埽可以通過JVM啟動參數(shù):-XX:+UseConcMarkSweepGC來開啟CMS瓣赂。

CMS收集過程

CMS 處理過程有七個步驟:

  1. 初始標(biāo)記(CMS-initial-mark) ,會導(dǎo)致stw;
  2. 并發(fā)標(biāo)記(CMS-concurrent-mark),與用戶線程同時運(yùn)行片拍;
  3. 預(yù)清理(CMS-concurrent-preclean)煌集,與用戶線程同時運(yùn)行;
  4. 可被終止的預(yù)清理(CMS-concurrent-abortable-preclean) 與用戶線程同時運(yùn)行捌省;
  5. 重新標(biāo)記(CMS-remark) 苫纤,會導(dǎo)致swt;
  6. 并發(fā)清除(CMS-concurrent-sweep)纲缓,與用戶線程同時運(yùn)行卷拘;
  7. 并發(fā)重置狀態(tài)等待下次CMS的觸發(fā)(CMS-concurrent-reset),與用戶線程同時運(yùn)行祝高;
    其運(yùn)行流程圖如下所示:
CMS收集過程.png

初始標(biāo)記

這是CMS中兩次stop-the-world事件中的一次栗弟。這一步的作用是標(biāo)記存活的對象,有兩部分:

  1. 標(biāo)記老年代中所有的GC Roots對象褂策,如下圖節(jié)點(diǎn)1横腿;
  2. 標(biāo)記年輕代中活著的對象引用到的老年代的對象(指的是年輕帶中還存活的引用類型對象颓屑,引用指向老年代中的對象)如下圖節(jié)點(diǎn)2斤寂、3;
CMS初始標(biāo)記

在Java語言里揪惦,可作為GC Roots對象的包括如下幾種:

  1. 虛擬機(jī)棧(棧楨中的本地變量表)中的引用的對象 遍搞;
  2. 方法區(qū)中的類靜態(tài)屬性引用的對象 ;
  3. 方法區(qū)中的常量引用的對象 器腋;
  4. 本地方法棧中JNI的引用的對象溪猿;
    ps:為了加快此階段處理速度,減少停頓時間纫塌,可以開啟初始標(biāo)記并行化诊县,-XX:+CMSParallelInitialMarkEnabled,同時調(diào)大并行標(biāo)記的線程數(shù)措左,線程數(shù)不要超過cpu的核數(shù)依痊。

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

從“初始標(biāo)記”階段標(biāo)記的對象開始找出所有存活的對象;
因?yàn)槭遣l(fā)運(yùn)行的,在運(yùn)行期間會發(fā)生新生代的對象晉升到老年代怎披、或者是直接在老年代分配對象胸嘁、或者更新老年代對象的引用關(guān)系等等瓶摆,對于這些對象,都是需要進(jìn)行重新標(biāo)記的性宏,否則有些對象就會被遺漏群井,發(fā)生漏標(biāo)的情況。為了提高重新標(biāo)記的效率毫胜,該階段會把上述對象所在的Card標(biāo)識為Dirty书斜,后續(xù)只需掃描這些Dirty Card的對象,避免掃描整個老年代指蚁;
并發(fā)標(biāo)記階段只負(fù)責(zé)將引用發(fā)生改變的Card標(biāo)記為Dirty狀態(tài)菩佑,不負(fù)責(zé)處理;
如下圖所示凝化,也就是節(jié)點(diǎn)1稍坯、2、3搓劫,最終找到了節(jié)點(diǎn)4和5瞧哟。并發(fā)標(biāo)記的特點(diǎn)是和應(yīng)用程序線程同時運(yùn)行。并不是老年代的所有存活對象都會被標(biāo)記枪向,因?yàn)闃?biāo)記的同時應(yīng)用程序會改變一些對象的引用等勤揩。
由于這個階段是和用戶線程并發(fā)的,可能會導(dǎo)致concurrent mode failure秘蛔。

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

預(yù)清理階段

前一個階段已經(jīng)說明陨亡,不能標(biāo)記出老年代全部的存活對象,是因?yàn)闃?biāo)記的同時應(yīng)用程序會改變一些對象引用深员,這個階段就是用來處理前一個階段因?yàn)橐藐P(guān)系改變導(dǎo)致沒有標(biāo)記到的存活對象的负蠕,它會掃描所有標(biāo)記為Dirty的Card
如下圖所示,在并發(fā)清理階段倦畅,節(jié)點(diǎn)3的引用指向了6遮糖;則會把節(jié)點(diǎn)3的card標(biāo)記為Dirty;

CMS預(yù)清理

最后將6標(biāo)記為存活,如下圖所示:

CMS預(yù)清理

可終止的預(yù)處理

這個階段嘗試著去承擔(dān)下一個階段Final Remark階段足夠多的工作叠赐。這個階段持續(xù)的時間依賴好多的因素欲账,由于這個階段是重復(fù)的做相同的事情直到發(fā)生abort的條件(比如:重復(fù)的次數(shù)、多少量的工作芭概、持續(xù)的時間等等)之一才會停止赛不。
ps:此階段最大持續(xù)時間為5秒,之所以可以持續(xù)5秒罢洲,另外一個原因也是為了期待這5秒內(nèi)能夠發(fā)生一次ygc踢故,清理年輕帶的引用,是的下個階段的重新標(biāo)記階段,掃描年輕帶指向老年代的引用的時間減少畴椰;

重新標(biāo)記

這個階段會導(dǎo)致第二次stop the word钝计,該階段的任務(wù)是完成標(biāo)記整個年老代的所有的存活對象牍疏。
這個階段挚赊,重新標(biāo)記的內(nèi)存范圍是整個堆苗踪,包含_young_gen和_old_gen。為什么要掃描新生代呢帚戳,因?yàn)閷τ诶夏甏械膶ο箸杌颍绻恍律械膶ο笠茫敲淳蜁灰暈榇婊顚ο笃危词剐律膶ο笠呀?jīng)不可達(dá)了偏友,也會使用這些不可達(dá)的對象當(dāng)做cms的“gc root”,來掃描老年代对供; 因此對于老年代來說位他,引用了老年代中對象的新生代的對象,也會被老年代視作“GC ROOTS”:當(dāng)此階段耗時較長的時候产场,可以加入?yún)?shù)-XX:+CMSScavengeBeforeRemark鹅髓,在重新標(biāo)記之前,先執(zhí)行一次ygc京景,回收掉年輕帶的對象無用的對象窿冯,并將對象放入幸存帶或晉升到老年代,這樣再進(jìn)行年輕帶掃描時确徙,只需要掃描幸存區(qū)的對象即可醒串,一般幸存帶非常小,這大大減少了掃描時間鄙皇。
由于之前的預(yù)處理階段是與用戶線程并發(fā)執(zhí)行的芜赌,這時候可能年輕帶的對象對老年代的引用已經(jīng)發(fā)生了很多改變,這個時候育苟,remark階段要花很多時間處理這些改變较鼓,會導(dǎo)致很長stop the word椎木,所以通常CMS盡量運(yùn)行Final Remark階段在年輕代是足夠干凈的時候违柏。
另外,還可以開啟并行收集:-XX:+CMSParallelRemarkEnabled香椎。

并發(fā)清理

通過以上5個階段的標(biāo)記漱竖,老年代所有存活的對象已經(jīng)被標(biāo)記并且現(xiàn)在要通過Garbage Collector采用清掃的方式回收那些不能用的對象了。
這個階段主要是清除那些沒有標(biāo)記的對象并且回收空間畜伐;
由于CMS并發(fā)清理階段用戶線程還在運(yùn)行著馍惹,伴隨程序運(yùn)行自然就還會有新的垃圾不斷產(chǎn)生,這一部分垃圾出現(xiàn)在標(biāo)記過程之后,CMS無法在當(dāng)次收集中處理掉它們万矾,只好留待下一次GC時再清理掉悼吱。這一部分垃圾就稱為“浮動垃圾”。

使用CMS需要注意的幾點(diǎn)

減少remark階段停頓

一般CMS的GC耗時80%都在remark階段良狈,如果發(fā)現(xiàn)remark階段停頓時間很長后添,可以嘗試添加該參數(shù):
-XX:+CMSScavengeBeforeRemark。
在執(zhí)行remark操作之前先做一次Young GC薪丁,目的在于減少年輕代對老年代的無效引用遇西,降低remark時的開銷。

內(nèi)存碎片問題

CMS是基于標(biāo)記-清除算法的严嗜,CMS只會刪除無用對象粱檀,不會對內(nèi)存做壓縮,會造成內(nèi)存碎片漫玄,這時候我們需要用到這個參數(shù):
-XX:CMSFullGCsBeforeCompaction=n
意思是說在上一次CMS并發(fā)GC執(zhí)行過后茄蚯,到底還要再執(zhí)行多少次full GC才會做壓縮。默認(rèn)是0睦优,也就是在默認(rèn)配置下每次CMS GC頂不住了而要轉(zhuǎn)入full GC的時候都會做壓縮第队。 如果把CMSFullGCsBeforeCompaction配置為10,就會讓上面說的第一個條件變成每隔10次真正的full GC才做一次壓縮刨秆。

concurrent mode failure

這個異常發(fā)生在cms正在回收的時候凳谦。執(zhí)行CMS GC的過程中,同時業(yè)務(wù)線程也在運(yùn)行衡未,當(dāng)年輕帶空間滿了尸执,執(zhí)行ygc時,需要將存活的對象放入到老年代缓醋,而此時老年代空間不足如失,這時CMS還沒有機(jī)會回收老年帶產(chǎn)生的,或者在做Minor GC的時候送粱,新生代救助空間放不下褪贵,需要放入老年代,而老年代也放不下而產(chǎn)生的抗俄。
設(shè)置cms觸發(fā)時機(jī)有兩個參數(shù):

  • -XX:+UseCMSInitiatingOccupancyOnly
  • -XX:CMSInitiatingOccupancyFraction=70

-XX:CMSInitiatingOccupancyFraction=70 是指設(shè)定CMS在對內(nèi)存占用率達(dá)到70%的時候開始GC脆丁。
-XX:+UseCMSInitiatingOccupancyOnly如果不指定, 只是用設(shè)定的回收閾值CMSInitiatingOccupancyFraction,則JVM僅在第一次使用設(shè)定值,后續(xù)則自動調(diào)整會導(dǎo)致上面的那個參數(shù)不起作用。

為什么要有這兩個參數(shù)动雹?
由于在垃圾收集階段用戶線程還需要運(yùn)行槽卫,那也就還需要預(yù)留有足夠的內(nèi)存空間給用戶線程使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進(jìn)行收集胰蝠,需要預(yù)留一部分空間提供并發(fā)收集時的程序運(yùn)作使用歼培。
CMS前五個階段都是標(biāo)記存活對象的震蒋,除了”初始標(biāo)記”和”重新標(biāo)記”階段會stop the word ,其它三個階段都是與用戶線程一起跑的躲庄,就會出現(xiàn)這樣的情況gc線程正在標(biāo)記存活對象查剖,用戶線程同時向老年代提升新的對象,清理工作還沒有開始噪窘,old gen已經(jīng)沒有空間容納更多對象了梗搅,這時候就會導(dǎo)致concurrent mode failure, 然后就會使用串行收集器回收老年代的垃圾效览,導(dǎo)致停頓的時間非常長无切。
CMSInitiatingOccupancyFraction參數(shù)要設(shè)置一個合理的值,設(shè)置大了丐枉,會增加concurrent mode failure發(fā)生的頻率哆键,設(shè)置的小了,又會增加CMS頻率瘦锹,所以要根據(jù)應(yīng)用的運(yùn)行情況來選取一個合理的值籍嘹。如果發(fā)現(xiàn)這兩個參數(shù)設(shè)置大了會導(dǎo)致full gc,設(shè)置小了會導(dǎo)致頻繁的CMS GC弯院,說明你的老年代空間過小辱士,應(yīng)該增加老年代空間的大小了。

promotion failed

在進(jìn)行Minor GC時听绳,Survivor Space放不下颂碘,對象只能放入老年代,而此時老年代也放不下造成的椅挣,多數(shù)是由于老年帶有足夠的空閑空間头岔,但是由于碎片較多,新生代要轉(zhuǎn)移到老年帶的對象比較大,找不到一段連續(xù)區(qū)域存放這個對象導(dǎo)致的鼠证。

過早提升與提升失敗

在 Minor GC 過程中峡竣,Survivor Unused 可能不足以容納 Eden 和另一個 Survivor 中的存活對象, 那么多余的將被移到老年代量九, 稱為過早提升(Premature Promotion),這會導(dǎo)致老年代中短期存活對象的增長适掰, 可能會引發(fā)嚴(yán)重的性能問題。 再進(jìn)一步荠列,如果老年代滿了类浪, Minor GC 后會進(jìn)行 Full GC, 這將導(dǎo)致遍歷整個堆弯予, 稱為提升失斊莼隆(Promotion Failure)个曙。

早提升的原因

  1. Survivor空間太小锈嫩,容納不下全部的運(yùn)行時短生命周期的對象受楼,如果是這個原因,可以嘗試將Survivor調(diào)大呼寸,否則端生命周期的對象提升過快艳汽,導(dǎo)致老年代很快就被占滿,從而引起頻繁的full gc对雪;
  2. 對象太大河狐,Survivor和Eden沒有足夠大的空間來存放這些大對象。

提升失敗原因

當(dāng)提升的時候瑟捣,發(fā)現(xiàn)老年代也沒有足夠的連續(xù)空間來容納該對象馋艺。為什么是沒有足夠的連續(xù)空間而不是空閑空間呢?老年代容納不下提升的對象有兩種情況:

  1. 老年代空閑空間不夠用了迈套;
  2. 老年代雖然空閑空間很多捐祠,但是碎片太多,沒有連續(xù)的空閑空間存放該對象桑李。

解決方法

  1. 如果是因?yàn)閮?nèi)存碎片導(dǎo)致的大對象提升失敗踱蛀,cms需要進(jìn)行空間整理壓縮;
  2. 如果是因?yàn)樘嵘^快導(dǎo)致的贵白,說明Survivor 空閑空間不足率拒,那么可以嘗試調(diào)大 Survivor;
  3. 如果是因?yàn)槔夏甏臻g不夠?qū)е碌慕模瑖L試將CMS觸發(fā)的閾值調(diào)低猬膨。

CMS相關(guān)參數(shù)

參數(shù) 類型 默認(rèn)值 說明
-XX:+UseConcMarkSweepGC boolean false 老年代采用CMS收集器收集
-XX:+CMSScavengeBeforeRemark boolean false The CMSScavengeBeforeRemark forces scavenge invocation from the CMS-remark phase (from within the VM thread as the CMS-remark operation is executed in the foreground collector).
-XX:+UseCMSCompactAtFullCollection boolean false 對老年代進(jìn)行壓縮,可以消除碎片呛伴,但是可能會帶來性能消耗
-XX:CMSFullGCsBeforeCompaction=n uintx 0 CMS進(jìn)行n次full gc后進(jìn)行一次壓縮寥掐。如果n=0,每次full gc后都會進(jìn)行碎片壓縮。如果n=0,每次full gc后都會進(jìn)行碎片壓縮
–XX:+CMSIncrementalMode boolean false 并發(fā)收集遞增進(jìn)行磷蜀,周期性把cpu資源讓給正在運(yùn)行的應(yīng)用
–XX:+CMSIncrementalPacing boolean false 根據(jù)應(yīng)用程序的行為自動調(diào)整每次執(zhí)行的垃圾回收任務(wù)的數(shù)量
–XX:ParallelGCThreads=n uintx (ncpus <= 8) ? ncpus : 3 + ((ncpus * 5) / 8) 并發(fā)回收線程數(shù)量
-XX:CMSIncrementalDutyCycleMin=n uintx 0 每次增量回收垃圾的占總垃圾回收任務(wù)的最小比例
-XX:CMSIncrementalDutyCycle=n uintx 10 每次增量回收垃圾的占總垃圾回收任務(wù)的比例
-XX:CMSInitiatingOccupancyFraction=n uintx jdk5 默認(rèn)是68% jdk6默認(rèn)92% 當(dāng)老年代內(nèi)存使用達(dá)到n%,開始回收召耘。CMSInitiatingOccupancyFraction = (100 - MinHeapFreeRatio) + (CMSTriggerRatio * MinHeapFreeRatio / 100)
-XX:CMSMaxAbortablePrecleanTime=n intx 5000 在CMS的preclean階段開始前,等待minor gc的最大時間褐隆。

總結(jié)

  1. CMS收集器只收集老年代污它,其以吞吐量為代價換取收集速度。
  2. CMS收集過程分為:初始標(biāo)記庶弃、并發(fā)標(biāo)記衫贬、預(yù)清理階段、可終止預(yù)清理歇攻、重新標(biāo)記和并發(fā)清理階段固惯。其中初始標(biāo)記和重新標(biāo)記是STW的。CMS大部分時間都花費(fèi)在重新標(biāo)記階段缴守,可以讓虛擬機(jī)先進(jìn)行一次Young GC葬毫,減少停頓時間镇辉。CMS無法解決"浮動垃圾"問題。
  3. 由于CMS的收集線程和用戶線程并發(fā)贴捡,可能在收集過程中出現(xiàn)"concurrent mode failure"忽肛,解決方法是讓CMS盡早GC。在一定次數(shù)的Full GC之后讓CMS對內(nèi)存做一次壓縮烂斋,減少內(nèi)存碎片屹逛,防止年輕代對象晉升到老年代時因?yàn)閮?nèi)存碎片問題導(dǎo)致晉升失敗。

參考資料

《深入理解Java虛擬機(jī)——JVM高級特性與最佳實(shí)踐》-周志明

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末汛骂,一起剝皮案震驚了整個濱河市罕模,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帘瞭,老刑警劉巖手销,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異图张,居然都是意外死亡锋拖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門祸轮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兽埃,“玉大人,你說我怎么就攤上這事适袜”恚” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵苦酱,是天一觀的道長售貌。 經(jīng)常有香客問我,道長疫萤,這世上最難降的妖魔是什么颂跨? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮扯饶,結(jié)果婚禮上恒削,老公的妹妹穿的比我還像新娘。我一直安慰自己尾序,他們只是感情好钓丰,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著每币,像睡著了一般携丁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兰怠,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天梦鉴,我揣著相機(jī)與錄音李茫,去河邊找鬼。 笑死尚揣,一個胖子當(dāng)著我的面吹牛涌矢,可吹牛的內(nèi)容都是我干的掖举。 我是一名探鬼主播快骗,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼塔次!你這毒婦竟也來了方篮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤励负,失蹤者是張志新(化名)和其女友劉穎藕溅,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體继榆,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡巾表,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了略吨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片集币。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖翠忠,靈堂內(nèi)的尸體忽然破棺而出鞠苟,到底是詐尸還是另有隱情,我是刑警寧澤秽之,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布当娱,位于F島的核電站,受9級特大地震影響考榨,放射性物質(zhì)發(fā)生泄漏跨细。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一河质、第九天 我趴在偏房一處隱蔽的房頂上張望扼鞋。 院中可真熱鬧,春花似錦愤诱、人聲如沸云头。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽溃槐。三九已至,卻和暖如春科吭,著一層夾襖步出監(jiān)牢的瞬間昏滴,已是汗流浹背猴鲫。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谣殊,地道東北人拂共。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像姻几,于是被迫代替她去往敵國和親宜狐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

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