日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

CMS垃圾收集器详解

發(fā)布時(shí)間:2023/12/20 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 CMS垃圾收集器详解 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

概述


CMS垃圾收集器是一款優(yōu)秀的老年代并發(fā)垃圾收集器,通過(guò)與用戶(hù)線(xiàn)程并發(fā)執(zhí)行的方式減少GC停頓的時(shí)間。本文主要聊一下CMS設(shè)計(jì)到的相關(guān)的數(shù)據(jù)結(jié)構(gòu)、具體的執(zhí)行過(guò)程、運(yùn)行中會(huì)出現(xiàn)的異常情況。

在CMS之前并行垃圾收集器通過(guò)下圖方式進(jìn)行,雖然GC階段多線(xiàn)程并行執(zhí)行單此時(shí)用戶(hù)線(xiàn)程是完全暫停的。如果GC時(shí)間過(guò)長(zhǎng),將引發(fā)服務(wù)響應(yīng)超時(shí)、調(diào)用接口超時(shí)等各類(lèi)異常。

而CMS垃圾收集器大部分時(shí)間GC線(xiàn)程與用戶(hù)線(xiàn)程并發(fā)執(zhí)行,只有在初始標(biāo)記和重新標(biāo)記階段才暫停用戶(hù)線(xiàn)程

總體思路:當(dāng)達(dá)到GC條件時(shí),開(kāi)始并發(fā)標(biāo)記存活的對(duì)象,并發(fā)的過(guò)程中記錄對(duì)象引用關(guān)系的變化。并發(fā)標(biāo)記結(jié)束后,暫停用戶(hù)線(xiàn)程,處理引用關(guān)系變化,得到所有存活的對(duì)象和可以清理的對(duì)象。最終并發(fā)清理掉不被使用的對(duì)象(存在已經(jīng)不被引用但本次清理不掉的對(duì)象)。
?

初始標(biāo)記

這是一個(gè)STW過(guò)程,主要分兩步
1、標(biāo)記GC Roots可達(dá)的老年代對(duì)象;
2、遍歷GC Roots下的新生代對(duì)象能夠可達(dá)的老年代對(duì)象;
3、此過(guò)程不對(duì)以上可達(dá)的老年代對(duì)象進(jìn)行進(jìn)一步的可達(dá)掃描。

暫停應(yīng)用程序線(xiàn)程,遍歷GC ROOTS直接可達(dá)的對(duì)象并將其壓入標(biāo)記棧(mark-stack)。標(biāo)記完之后恢復(fù)應(yīng)用程序線(xiàn)程。

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

這個(gè)階段虛擬機(jī)會(huì)分出若干線(xiàn)程(GC 線(xiàn)程)去進(jìn)行并發(fā)標(biāo)記。標(biāo)記哪些對(duì)象呢,標(biāo)記那些GC ROOTS最終可達(dá)的對(duì)象。具體做法是推出標(biāo)記棧里面的對(duì)象,然后遞歸標(biāo)記其直接引用的子對(duì)象(如果遇到地址比當(dāng)前對(duì)象低的對(duì)象則標(biāo)記并壓如棧中,遇到地址比當(dāng)前對(duì)象高則只標(biāo)記不入棧,同樣的把子對(duì)象壓到標(biāo)記棧中,重復(fù)推出,壓入。。。直至清空標(biāo)記棧。這個(gè)階段GC線(xiàn)程和應(yīng)用程序線(xiàn)程同時(shí)運(yùn)行。

標(biāo)記過(guò)程舉例
標(biāo)記的過(guò)程伴隨著對(duì)象引用的修改,下圖舉例說(shuō)明了并發(fā)標(biāo)記的過(guò)程

初始標(biāo)記(圖中未體現(xiàn),參考步驟a):初始標(biāo)記時(shí)對(duì)a進(jìn)行了標(biāo)記。
步驟a:對(duì)a引用的對(duì)象bc,及b引用的對(duì)象e進(jìn)行標(biāo)記,根據(jù)當(dāng)時(shí)的對(duì)象引用關(guān)系,abcegd是存活的。
步驟b:對(duì)象引用關(guān)系發(fā)生了改變,b不再引用c新增引用d,g不再引用d
步驟c:完成了并發(fā)標(biāo)記的過(guò)程,abceg被標(biāo)記。第二個(gè)和第四個(gè)區(qū)域內(nèi)的對(duì)象引用關(guān)系發(fā)生了改變,被記錄了下來(lái)。這里面運(yùn)用到了card table、mod union table數(shù)據(jù)結(jié)構(gòu)和write barrier技術(shù),后面進(jìn)行說(shuō)明。
步驟d:實(shí)際上是重新標(biāo)記后的結(jié)果,可以看到對(duì)象d在并發(fā)標(biāo)記結(jié)束時(shí)未進(jìn)行標(biāo)記,但是它還在被對(duì)象b引用,不應(yīng)該回收。這就依賴(lài)重新標(biāo)記階段對(duì)dirty card(對(duì)象引用關(guān)系發(fā)生變化的區(qū)域)的處理。
?

這個(gè)過(guò)程應(yīng)用線(xiàn)程在運(yùn)行,可能Young GC也會(huì)發(fā)生,會(huì)發(fā)生以下的情況:
1、新生代對(duì)象晉升到老年代
2、在老年代分配對(duì)象
3、新老年代對(duì)象的引用發(fā)生變化

三色標(biāo)記詳解:三色標(biāo)記法與讀寫(xiě)屏障 - 簡(jiǎn)書(shū)

這種條件下可能會(huì)出現(xiàn)活動(dòng)對(duì)象的漏標(biāo)的情況,比如下面場(chǎng)景:

活動(dòng)對(duì)象被遺漏標(biāo)記

A是活動(dòng)對(duì)象,A->B,標(biāo)記B可達(dá),將其壓入標(biāo)記棧,此時(shí)A所有直接子對(duì)象遍歷完,A出棧,標(biāo)記線(xiàn)程將不會(huì)再訪(fǎng)問(wèn)A。

同時(shí)應(yīng)用程序移掉了B對(duì)C的引用,讓A重新引用C。

B出棧時(shí)無(wú)法標(biāo)記C可達(dá),A雖然引用C但標(biāo)記線(xiàn)程不會(huì)再訪(fǎng)問(wèn)A,此時(shí)C會(huì)被當(dāng)成不可達(dá)對(duì)象

并發(fā)過(guò)程中變化的維護(hù)card table與mod union table

card table

CMS中一個(gè)與YGC相關(guān)并十分重要的數(shù)據(jù)結(jié)構(gòu)是:卡表(card table)。之所以出現(xiàn)卡表這樣的一個(gè)數(shù)據(jù)結(jié)構(gòu)是因?yàn)?#xff1a;YGC時(shí)為了標(biāo)記活動(dòng)標(biāo)記對(duì)象除了tracing GC ROOTS之外, 別忘了老年代里也可能會(huì)引用新生代對(duì)象。所以正常來(lái)說(shuō)還要掃描一次老年代,如果是掃描整個(gè)老年代這將會(huì)隨著堆的增大變得越來(lái)越慢,特別是現(xiàn)在內(nèi)存都越來(lái)越大了。所以為了提升性能就引入卡表。

卡表提升性能的原理:邏輯上把老年代內(nèi)存分成一個(gè)個(gè)大小相等的卡頁(yè)(card page,大小512byte),然后對(duì)每個(gè)卡片準(zhǔn)備一個(gè)與其對(duì)應(yīng)的標(biāo)記位,并將這些位集中起管理就好像一個(gè)表格(mark table)一樣,當(dāng)改寫(xiě)對(duì)象引用是從老年代指向新生代時(shí),在老年代對(duì)應(yīng)的卡片標(biāo)記位上設(shè)置標(biāo)志位即可,通常這樣的卡片我們稱(chēng)之為dirty card。這項(xiàng)操作可以通過(guò)上面的提到的write barrier來(lái)實(shí)現(xiàn),這樣就算對(duì)象跨多張卡片也不會(huì)有什么問(wèn)題。卡表通常是用byte數(shù)組實(shí)現(xiàn)的,byte的值只能取[0,1]這兩種。所以btye[i] = 1 就表示第i + 1 卡片所在內(nèi)存上有指向新生代引用的老年代對(duì)象,這時(shí)只要tracing這個(gè)卡片上的對(duì)象即可。如果每個(gè)card大小的是128字節(jié)(1024位,)那卡表就只占整個(gè)老年代的1/1024之一。所以遍歷卡表的時(shí)間會(huì)遠(yuǎn)比遍歷整個(gè)老年代快得多!這其中背后思想就是典型以空間換時(shí)間的思路!這種思路在G1中也有體現(xiàn),只不其對(duì)應(yīng)的數(shù)據(jù)是remember set而已。

對(duì)于新生代:它記錄老年代到新生代的引用,younggc時(shí)不用遍歷整個(gè)老年代

對(duì)于老年代:它記錄并發(fā)標(biāo)記開(kāi)始引用發(fā)生變化的card,并發(fā)標(biāo)記結(jié)束后需要處理這些card

由于新生代GC與老年代GC同時(shí)使用card table,所以會(huì)出現(xiàn)沖突的情況

Young GC如果發(fā)生,比方說(shuō):
1、并發(fā)標(biāo)記還未掃描到臟卡1.
2、Young GC掃描完臟卡,并改變dirty到clean.
3、并發(fā)標(biāo)記掃描,發(fā)現(xiàn)卡1已不是臟卡,則不會(huì)處理,這就造成了漏標(biāo)。

但這個(gè)card必須在remark階段進(jìn)行重新標(biāo)記。所以增加了另一個(gè)數(shù)據(jù)結(jié)構(gòu)mod union table解決此問(wèn)題。

mod union table是一個(gè)bit位向量,一個(gè)bit表示一個(gè)card的狀態(tài)。

它由新生代垃圾收集器維護(hù),新生代GC將card設(shè)置為clean之前,把mod union table設(shè)置為dirty。card table狀態(tài)為dirty、或者mod union table標(biāo)記為dirty、或者同時(shí)兩種數(shù)據(jù)結(jié)構(gòu)都標(biāo)記為dirty的card表示并發(fā)標(biāo)記階段引用發(fā)生了變化,需要在后面的階段進(jìn)行處理。

write barrie?

write barrie寫(xiě)屏障類(lèi)似于一個(gè)切面,用戶(hù)線(xiàn)程寫(xiě)對(duì)象引用的時(shí)候就觸發(fā)write barrier的邏輯,將對(duì)象所處的card設(shè)置為dirty。

并發(fā)預(yù)清理concurrent preclean

通過(guò)參數(shù)CMSPrecleaningEnabled選擇關(guān)閉該階段,默認(rèn)啟用,主要做兩件事情:

1、處理新生代已經(jīng)發(fā)現(xiàn)的引用,比如在并發(fā)階段,在Eden區(qū)中分配了一個(gè)A對(duì)象,A對(duì)象引用了一個(gè)老年代對(duì)象B(這個(gè)B之前沒(méi)有被標(biāo)記),在這個(gè)階段就會(huì)標(biāo)記對(duì)象B為活躍對(duì)象。
2、在并發(fā)標(biāo)記階段,如果老年代中有對(duì)象內(nèi)部引用發(fā)生變化,會(huì)把所在的Card標(biāo)記為Dirty(包括ModUnionTalble),通過(guò)掃描這些Table,重新標(biāo)記那些在并發(fā)標(biāo)記階段引用被更新的對(duì)象。

處理dirty card,降低remark階段暫停時(shí)間。

重新標(biāo)記的過(guò)程是STW的,所以為了縮短停頓時(shí)間,在并發(fā)標(biāo)記之前應(yīng)該盡可能多的完成重新標(biāo)記階段的工作。并發(fā)預(yù)清理就是對(duì)dirty card進(jìn)行遍歷處理,降低重新標(biāo)記需要處理的dirty card的數(shù)量。

可中斷預(yù)清理concurrent abortable preclean

該階段發(fā)生的前提是,新生代Eden區(qū)的內(nèi)存使用量大于參數(shù)CMSScheduleRemarkEdenSizeThreshold 默認(rèn)是2M,如果新生代的對(duì)象太少,就沒(méi)有必要執(zhí)行該階段,直接執(zhí)行重新標(biāo)記階段。

為什么需要這個(gè)階段,存在的價(jià)值是什么?

因?yàn)镃MS GC的終極目標(biāo)是降低垃圾回收時(shí)的暫停時(shí)間,所以在該階段要盡最大的努力去處理那些在并發(fā)階段被應(yīng)用線(xiàn)程更新的老年代對(duì)象,這樣在暫停的重新標(biāo)記階段就可以少處理一些,暫停時(shí)間也會(huì)相應(yīng)的降低。

在該階段,主要循環(huán)的做兩件事:

1、處理 From 和 To 區(qū)的對(duì)象,標(biāo)記可達(dá)的老年代對(duì)象
2、和上一個(gè)階段一樣,掃描處理Dirty Card和ModUnionTalble中的對(duì)象。

當(dāng)然了,這個(gè)邏輯不會(huì)一直循環(huán)下去,打斷這個(gè)循環(huán)的條件有三個(gè):

1、可以設(shè)置最多循環(huán)的次數(shù) CMSMaxAbortablePrecleanLoops,默認(rèn)是0,意思沒(méi)有循環(huán)次數(shù)的限制。
2、如果執(zhí)行這個(gè)邏輯的時(shí)間達(dá)到了閾值CMSMaxAbortablePrecleanTime,默認(rèn)是5s,會(huì)退出循環(huán)。
3、如果新生代Eden區(qū)的內(nèi)存使用率達(dá)到了閾值CMSScheduleRemarkEdenPenetration,默認(rèn)50%,會(huì)退出循環(huán)。

重新標(biāo)記final remark

在之前的并行階段,可能產(chǎn)生新的引用關(guān)系如下:

1、老年代的新對(duì)象被GC Roots引用
2、老年代的未標(biāo)記對(duì)象被新生代對(duì)象引用
3、老年代已標(biāo)記的對(duì)象增加新引用指向老年代其它對(duì)象
4、新生代對(duì)象指向老年代引用被刪除

上述對(duì)象中可能有一些已經(jīng)在Precleaning階段和AbortablePreclean階段被處理過(guò),但總存在沒(méi)來(lái)得及處理的,所以需要做以下事情:

1、遍歷新生代對(duì)象,重新標(biāo)記
2、根據(jù)GC Roots,重新標(biāo)記
3、遍歷老年代的Dirty Card和Mod Union Table,重新標(biāo)記

在第1步驟中,需要遍歷新生代的全部對(duì)象,如果新生代的使用率很高,需要遍歷處理的對(duì)象也很多,這對(duì)于這個(gè)階段的總耗時(shí)來(lái)說(shuō),是個(gè)災(zāi)難(因?yàn)榭赡艽罅康膶?duì)象是暫時(shí)存活的,而且這些對(duì)象也可能引用大量的老年代對(duì)象,造成很多應(yīng)該回收的老年代對(duì)象而沒(méi)有被回收,遍歷遞歸的次數(shù)也增加不少),如果在這之前發(fā)生一次YGC,這樣就可以避免掃描無(wú)效的對(duì)象。

CMS算法中提供了一個(gè)參數(shù):CMSScavengeBeforeRemark,默認(rèn)并沒(méi)有開(kāi)啟,如果開(kāi)啟該參數(shù),在執(zhí)行該階段之前,會(huì)強(qiáng)制觸發(fā)一次YGC,可以減少新生代對(duì)象的遍歷時(shí)間,回收的也更徹底一點(diǎn)。

并發(fā)清除concurrent sweep

并發(fā)清除標(biāo)記為不可達(dá)的對(duì)象,回收并合并空閑內(nèi)存。

并發(fā)重置concurrent reset

重新設(shè)置CMS相關(guān)的各種狀態(tài)及數(shù)據(jù)結(jié)構(gòu),為下一個(gè)垃圾收集周期做好準(zhǔn)備。

缺點(diǎn)
cpu資源敏感,降低吞吐量
CMS沒(méi)有運(yùn)行的時(shí)候所有全部cpu資源都供用戶(hù)線(xiàn)程使用,CMS開(kāi)始并發(fā)運(yùn)行后就要跟用戶(hù)線(xiàn)程競(jìng)爭(zhēng)cpu資源,導(dǎo)致應(yīng)用線(xiàn)程運(yùn)行變慢。對(duì)于cpu資源非常緊缺的系統(tǒng),假設(shè)只有2核,CMS運(yùn)行起來(lái)后將占用一半的cpu資源,用戶(hù)線(xiàn)程將感知到運(yùn)行速度減半。

并發(fā)帶來(lái)的好處是可以降低用戶(hù)線(xiàn)程的停頓時(shí)間,對(duì)于在線(xiàn)服務(wù)類(lèi)應(yīng)用非常有益,因?yàn)殚L(zhǎng)時(shí)間的停頓可能導(dǎo)致響應(yīng)超時(shí)等問(wèn)題。但相對(duì)于非并發(fā)垃圾收集器,CMS整個(gè)周期內(nèi)很多工作是重復(fù)的(比如重新標(biāo)記階段對(duì)dirty card中的對(duì)象重新標(biāo)記,而在并發(fā)標(biāo)記階段可能已經(jīng)標(biāo)記過(guò)了),導(dǎo)致整體的吞吐量是降低的。

浮動(dòng)垃圾


因?yàn)镃MS垃圾收集器的特性,被標(biāo)記過(guò)的對(duì)象,即使最終變成垃圾本次GC也不會(huì)回收它,這些垃圾就是浮動(dòng)垃圾。浮動(dòng)垃圾的產(chǎn)生意味著內(nèi)存里不光裝著存活對(duì)象,還要裝著這些浮動(dòng)垃圾,所以容納同樣多的存活對(duì)象CMS需要占用更大的內(nèi)存空間。

內(nèi)存碎片


CMS使用標(biāo)記清除算法,收集結(jié)束之后會(huì)產(chǎn)生大量?jī)?nèi)存碎片。當(dāng)有大對(duì)象需要分配空間時(shí),可能總的空間大小是足夠的,但是沒(méi)有連續(xù)的空間裝下此對(duì)象。

CMS默認(rèn)開(kāi)啟UseCMSCompactAtFullCollection?參數(shù),在FullGC時(shí)進(jìn)行內(nèi)存碎片的合并整理。內(nèi)存碎片雖然解決了,但負(fù)面影響就是停頓時(shí)間變長(zhǎng)了。還有另外一個(gè)CMSFullGCsBeforeCompaction參數(shù)可以控制多少次FullGC才會(huì)進(jìn)行整理,默認(rèn)是0代表每次FullGC都會(huì)進(jìn)行碎片整理。

運(yùn)行過(guò)程常見(jiàn)問(wèn)題


concurrent mode failure
并發(fā)雖好,但會(huì)引入一些問(wèn)題。對(duì)于非并發(fā)的垃圾收集器,可以等到老年代無(wú)法分配對(duì)象時(shí)再執(zhí)行GC。但對(duì)于cms則需要預(yù)留出空間提前開(kāi)始GC,預(yù)留的空間供并發(fā)期間新對(duì)象的分配及新生代對(duì)象的晉升使用。如果在老年代分配對(duì)象發(fā)現(xiàn)老年代裝不下,則會(huì)觸發(fā)concurrent mode failure,此時(shí)將會(huì)暫停用戶(hù)線(xiàn)程執(zhí)行FullGC或者串行模式的CMS。

promotion failed
這個(gè)錯(cuò)誤涉及到CMS擔(dān)保機(jī)制,新生代GC之前會(huì)根據(jù)歷史晉升到老年代對(duì)象的大小,預(yù)估本次老年代是否足夠容納新生代晉升的對(duì)象。如果預(yù)估時(shí)空間足夠,但新生代GC實(shí)際執(zhí)行時(shí)發(fā)現(xiàn)容納不了,則會(huì)引起promotion failed錯(cuò)誤。

OutOfMemoryError?
CMS垃圾收集器發(fā)現(xiàn)大部分時(shí)間都浪費(fèi)在GC上就會(huì)拋出OutOfMemoryError異常,具體為98%的時(shí)間在GC但回收不到2%的空間。這樣做實(shí)際上是為了防止程序進(jìn)入一種雖然在運(yùn)行實(shí)際上一直在GC假死狀態(tài),也可以通過(guò)設(shè)置-XX:-UseGCOverheadLimit禁用該機(jī)制。

?
原文鏈接:https://blog.csdn.net/hongxingxiaonan/article/details/105019325

總結(jié)

以上是生活随笔為你收集整理的CMS垃圾收集器详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。