<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JVM 面試必問(wèn)的 CMS,你懂了嗎?

          共 6867字,需瀏覽 14分鐘

           ·

          2021-11-01 19:15

          前言

          雖然 CMS 已經(jīng)是很古老的垃圾回收器了,大家現(xiàn)在動(dòng)不動(dòng)就G1、ZGC啥的,但是據(jù)我所了解,還是有很多公司的生產(chǎn)環(huán)境主要使用的 CMS,包括我自己呆過(guò)的幾家大廠也是。

          因此在 JVM 面試中,CMS 也是問(wèn)的最多的,包括我自己現(xiàn)在面試別人時(shí),問(wèn)到 JVM 這一塊,我也喜歡從CMS開(kāi)始,逐漸深入。

          不多廢話,今天我們就來(lái)盤(pán)他。


          正文

          1、什么是卡表(card table)?

          試想一下,在進(jìn)行 YGC 時(shí),如何判斷是否存在老年代到新生代的引用?

          一個(gè)簡(jiǎn)單的辦法是掃描整個(gè)老年代,但是這個(gè)代價(jià)太大了,因此 JVM 引入了卡表來(lái)解決這個(gè)問(wèn)題。

          卡表又稱為卡片標(biāo)記(card marking),由 Paul R. Wilson 和 Thomas G. Moher 在1989年發(fā)表的論文里提出。

          其原理為,在邏輯上將老年代空間分割為若干個(gè)固定大小的連續(xù)區(qū)域,分割出來(lái)的每一個(gè)個(gè)區(qū)域就稱為卡片(card)。另外,為每個(gè)卡片準(zhǔn)備一個(gè)與其對(duì)應(yīng)的標(biāo)記位,最簡(jiǎn)單的實(shí)現(xiàn)方案是由字節(jié)數(shù)組實(shí)現(xiàn),以卡的編號(hào)作為索引。個(gè)卡的大小通常介于128~512字節(jié)之間一般使用2的冪字節(jié)大小,例如HotSpot使用512字節(jié)。

          當(dāng)卡片內(nèi)部發(fā)生引用變化時(shí)(指針寫(xiě)操作),寫(xiě)屏障會(huì)將該卡在卡表中對(duì)應(yīng)的字節(jié)標(biāo)記為臟(dirty)。

          有了卡表后,在 YGC 時(shí),只需將卡表中被標(biāo)記為 dirty 的 card 也作為掃描范圍,就可以保障不掃描整個(gè)老年代也不會(huì)有遺漏了。


          2、什么是 mod-union table

          通過(guò)上面對(duì) card table 的介紹,我們知道 card table 會(huì)記錄下老年代所有發(fā)生過(guò)引用變化對(duì)象所在的 card,而 CMS 在并發(fā)標(biāo)記等階段,也需要記錄下老年代發(fā)生引用變化的對(duì)象以便后續(xù)重新掃描,是否可以直接復(fù)用 card table?

          答案是不行的,這是因?yàn)?/span>每次 YGC 過(guò)程中都涉及重置和重新掃描 card table,這樣是滿足了 YGC 的需求,但卻破壞了CMS的需求,CMS 需要的信息可能被 YGC 給重置掉了。為了避免丟失信息,于是在 card table 之外另外加了一個(gè) Bitmap 叫做 mod-union table。

          在 CMS 并發(fā)標(biāo)記正在運(yùn)行的過(guò)程中,每當(dāng)發(fā)生一次 YGC,當(dāng) YGC 要重置 card table 里的某個(gè)記錄時(shí),就會(huì)更新 mod-union table 對(duì)應(yīng)的 bit,相當(dāng)于將 card table 里的信息轉(zhuǎn)移到了 mod-union table 里。

          這樣,最后到 Final remark 的時(shí)候,card table 加 mod-union table 就足以記錄在并發(fā)標(biāo)記過(guò)程中老年代發(fā)生的所有引用變化了。


          3、CMS 垃圾收集的過(guò)程?

          CMS 垃圾收集的過(guò)程網(wǎng)上通常有兩個(gè)版本,4個(gè)步驟的和7個(gè)步驟的,兩個(gè)版本其實(shí)都是對(duì)的。

          4個(gè)步驟應(yīng)該主要是跟隨周志明的說(shuō)法,而 CMS 的相關(guān)論文其實(shí)也是按4個(gè)步驟介紹。

          7個(gè)步驟則應(yīng)該更多是從 CMS 的日志得出的說(shuō)法,而7個(gè)步驟里其實(shí)也包含了上述的4個(gè)步驟,可以理解為7個(gè)步驟是更細(xì)的說(shuō)法。

          個(gè)人而言,我會(huì)更喜歡7個(gè)步驟的說(shuō)法,因此這邊介紹下7個(gè)步驟的過(guò)程。

          1)初始標(biāo)記(Initial Mark

          STW(stop the world),遍歷 GC Roots,標(biāo)記 GRoot 直達(dá)的對(duì)象。

          2)并發(fā)標(biāo)記(Concurrent Mark)

          從初始標(biāo)記階段被標(biāo)記為存活的對(duì)象作為起點(diǎn),向下遍歷,找出所有存活的對(duì)象。

          同時(shí),由于該階段是用戶線程和GC線程并發(fā)執(zhí)行,對(duì)象之間的引用關(guān)系在不斷發(fā)生變化,對(duì)于這些對(duì)象,都是需要進(jìn)行重新標(biāo)記的,否則就會(huì)出現(xiàn)錯(cuò)誤。為了提升重新標(biāo)記的效率,JVM 會(huì)使用寫(xiě)屏障(write barrier)將發(fā)生引用關(guān)系變化的對(duì)象所在的區(qū)域?qū)?yīng)的 card 標(biāo)記為 dirty,后續(xù)只需要掃描這些 dirty card 區(qū)域即可,避免掃描整個(gè)老年代。

          3)并發(fā)預(yù)處理(Concurrent Preclean

          該階段存在的意義主要是為了盡可能降低 Final Remark 階段的耗時(shí),因?yàn)?nbsp;Final Remark 階段是 STW 的。

          該階段主要做的事是將上一階段被標(biāo)記為 dirty 的 card 所對(duì)應(yīng)的區(qū)域進(jìn)行重新掃描標(biāo)記,處理并發(fā)階段發(fā)生引用變化的對(duì)象。

          4)可中斷的并發(fā)預(yù)處理Concurrent Abortable Preclean

          該階段和并發(fā)預(yù)處理做的事是基本一樣的,也是主要處理 dirty card。區(qū)別在于并發(fā)預(yù)處理只執(zhí)行一次,而本階段會(huì)一直循環(huán)執(zhí)行,直到觸發(fā)終止條件。

          終止條件有以下幾個(gè)

          • 循環(huán)次數(shù)超過(guò)閾值 CMSMaxAbortablePrecleanLoops,默認(rèn)是0,也就是沒(méi)有循環(huán)次數(shù)的限制。

          • 處理時(shí)間達(dá)到了閾值 CMSMaxAbortablePrecleanTime,默認(rèn)是5秒。

          • Eden區(qū)的內(nèi)存使用率達(dá)到了閾值 CMSScheduleRemarkEdenPenetration,默認(rèn)為50%。

          同時(shí)該階段有一個(gè)觸發(fā)前提:

          • Eden 區(qū)的內(nèi)存使用量大于參數(shù)CMSScheduleRemarkEdenSizeThreshold,默認(rèn)是2M。

          5)最終標(biāo)記/重新標(biāo)記(Final Remark

          STW(stop the world),主要做兩件事:

          • 遍歷 GCRoots,重新掃描標(biāo)記

          • 遍歷被標(biāo)記為 dirty 的 card,重新掃描標(biāo)記

          6)并發(fā)清理(Concurrent Sweep

          清理未使用的對(duì)象并回收它們占用的空間。

          7)并發(fā)重置(Concurrent Reset

          重置 CMS 算法用于打標(biāo)的數(shù)據(jù)結(jié)構(gòu)(markBitMap),為下一次收集做準(zhǔn)備。


          4、CMS存在的問(wèn)題

          1)使用的標(biāo)記-清除算法,可能存在大量空間碎片。

          調(diào)優(yōu):開(kāi)啟CMS壓縮,查看參數(shù)是否合理。

          // 開(kāi)啟CMS壓縮,在FGC時(shí)執(zhí)行壓縮,默認(rèn)為true-XX:+UseCMSCompactAtFullCollection // 執(zhí)行幾次FGC才執(zhí)行壓縮,默認(rèn)為0-XX:CMSFullGCsBeforeCompaction=0 

          2)并發(fā)清理可能出現(xiàn)“Concurrent Mode Failure”失敗而導(dǎo)致另一次Full GC的產(chǎn)生

          調(diào)優(yōu):可能是觸發(fā)GC的比例太高,適當(dāng)調(diào)低該值。

          // CMS觸發(fā)GC的比例-XX:+UseCMSInitiatingOccupancyOnly-XX:+CMSInitiatingOccupancyFraction=70

          3)對(duì)CPU資源非常敏感。在并發(fā)階段,會(huì)因?yàn)檎加昧艘徊糠志€程(或者說(shuō)CPU資源)而導(dǎo)致應(yīng)用程序變慢,總吞吐量會(huì)降低。CMS默認(rèn)啟動(dòng)的回收線程數(shù)是(CPU數(shù)量+3)/4。

          調(diào)優(yōu):可能是并發(fā)線程數(shù)設(shè)置太高,適當(dāng)調(diào)低該值。

          // CMS并發(fā)線程數(shù)-XX:ConcGCThreads=X

          以上的調(diào)優(yōu)只是針對(duì)一些可能性較大的問(wèn)題給的建議,具體還是需要結(jié)合場(chǎng)景和完整的JVM參數(shù)去分析,各個(gè)參數(shù)可能都會(huì)影響到整體的GC效率。


          5、Final Remark 階段為什么還需要遍歷 GCRoots?

          這是因?yàn)?CMS 的寫(xiě)屏障(write barrier并不是對(duì)所有會(huì)導(dǎo)致引用變化的字節(jié)碼生效,例如不支持 astore_X(把棧頂?shù)闹荡娴奖镜刈兞勘恚?/span>

          至于為什么不為 astore_X 添加寫(xiě)屏障,R 大認(rèn)為是棧和年輕代屬于數(shù)據(jù)快速變化的區(qū)域,對(duì)于這些區(qū)域使用寫(xiě)屏障的收益比較差。


          6、Final Remark 階段還需要遍歷 GC Roots,那之前的標(biāo)記工作不是白做了?

          不是的。

          在三色標(biāo)記法中(見(jiàn)下面介紹),如果掃描到被標(biāo)記為黑色的對(duì)象就會(huì)終止,而之前的并發(fā)標(biāo)記和預(yù)處理已經(jīng)完成了絕大部分對(duì)象的標(biāo)記,也就是此時(shí)大部分對(duì)象已經(jīng)是黑色了,因此 Final Remark 階段的工作其實(shí)會(huì)減少很多。簡(jiǎn)單來(lái)說(shuō)就是:遍歷的廣度不變,但是深度變淺了。


          7、三色標(biāo)記算法?

          三色標(biāo)記算法由 Edsger W. Dijkstra 等人在1978年提出,是一種增量式垃圾回收算法,增量式的意思是慢慢發(fā)生變化的意思,也就是 GC 和 mutator(應(yīng)用程序)一點(diǎn)點(diǎn)交替運(yùn)行的手法。

          與其相反的則是停止型GC,也就是GC時(shí),mutator 完全停止,GC結(jié)束再恢復(fù)運(yùn)行。

          三色標(biāo)記算法顧名思義就是將 GC 中的對(duì)象分為三種顏色,這三種顏色和所包含的意思如下:

          白色:還未搜索過(guò)的對(duì)象。在回收周期的開(kāi)始階段,所有對(duì)象都為白色,而在回收周期結(jié)束時(shí),所有白色對(duì)象均為不可達(dá)對(duì)象,也就是要回收的對(duì)象。

          灰色:正在搜索的對(duì)象。已經(jīng)被搜索過(guò)的對(duì)象,但是該對(duì)象引用的對(duì)象還未被全部搜索完畢。

          黑色:搜索完成的對(duì)象。本身及其引用的所有對(duì)象都被搜索過(guò),黑色對(duì)象不會(huì)指向白色對(duì)象,同時(shí)黑色對(duì)象不會(huì)被重新搜索,除非顏色發(fā)生變化。

          我們以 GC 標(biāo)記-清除算法為例簡(jiǎn)單的說(shuō)明一下。

          GC 開(kāi)始運(yùn)行前所有的對(duì)象都是白色。GC 一開(kāi)始運(yùn)行,所有從根能到達(dá)的對(duì)象都會(huì)被標(biāo)記為灰色,然后被放到棧里。GC 只是發(fā)現(xiàn)了這樣的對(duì)象,但還沒(méi)有搜索完它們,所以這些對(duì)象就成了灰色對(duì)象。

          灰色對(duì)象會(huì)被依次從棧中取出,其子對(duì)象也會(huì)被涂成灰色。當(dāng)其所有的子對(duì)象都被涂成灰色時(shí),該對(duì)象就會(huì)被涂成黑色。當(dāng) GC 結(jié)束時(shí)已經(jīng)不存在灰色對(duì)象了,活動(dòng)對(duì)象全部為黑色,垃圾則為白色。

          下面是一個(gè)三色標(biāo)記算法的示例動(dòng)圖,大家參考著理解。

          748026ed8acf5e38d412696f8e43b192.webp

          明白了三色標(biāo)記算法后,再回過(guò)頭去看第5題,是不是頓時(shí)就明白了。


          8、三色標(biāo)記算法存在的問(wèn)題?

          三色標(biāo)記算法是增量式垃圾回收算法,mutator可能會(huì)隨時(shí)改變對(duì)象引用關(guān)系,因此在并發(fā)下會(huì)存在漏標(biāo)和錯(cuò)標(biāo)(多標(biāo))。

          1)漏標(biāo)

          直接通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)看:

          假設(shè)當(dāng)GC線程執(zhí)行到時(shí)刻1時(shí),此時(shí)應(yīng)用線程先執(zhí)行了步驟1和2,也就是到了時(shí)刻3的場(chǎng)景,GC線程繼續(xù)執(zhí)行。

          此時(shí)對(duì)象Z只被黑色對(duì)象X所引用,而黑色對(duì)象是不會(huì)被繼續(xù)掃描的,因此掃描結(jié)束后Z仍然是白色對(duì)象,也就是時(shí)刻4,此時(shí)白色對(duì)象Z則會(huì)被當(dāng)做垃圾而回收。

          f31a8de372888bdb85af96b860612441.webp

          2)錯(cuò)標(biāo)(標(biāo)

          直接通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)看:

          假設(shè)當(dāng)GC線程執(zhí)行到時(shí)刻1時(shí),此時(shí)應(yīng)用線程先執(zhí)行了步驟1,也就是到了時(shí)刻2的場(chǎng)景,GC線程繼續(xù)執(zhí)行。

          此時(shí)對(duì)象Z是灰色對(duì)象,GC線程對(duì)其進(jìn)行搜索,搜索結(jié)束后將其標(biāo)記為黑色,也就是時(shí)刻3,此時(shí)對(duì)象Z其實(shí)沒(méi)有到GC Roots的引用,理應(yīng)被回收,但是因?yàn)楸诲e(cuò)誤的標(biāo)記為黑色,而在本次GC中存活了下來(lái)。



          d784210023338303f488503542859713.webp


          錯(cuò)標(biāo)和漏標(biāo)都是三色標(biāo)記算法存在的問(wèn)題,但是兩者帶來(lái)的后果有本質(zhì)的不同。

          錯(cuò)標(biāo)使得死亡的對(duì)象被當(dāng)做存活,導(dǎo)致出現(xiàn)浮動(dòng)垃圾,此時(shí)不影響程序的正確性,這些對(duì)象下次GC時(shí)回收就可以了。

          漏標(biāo)使得存活的對(duì)象被當(dāng)做死亡,這個(gè)會(huì)導(dǎo)致程序出錯(cuò),帶來(lái)不可預(yù)知的后果,這個(gè)是不能接受的,因此漏標(biāo)是三色標(biāo)記算法需要解決的問(wèn)題。

          通過(guò)實(shí)驗(yàn)追蹤,Wilson 發(fā)現(xiàn),只有當(dāng)以下兩個(gè)條件同時(shí)滿足時(shí)才會(huì)出現(xiàn)漏標(biāo)問(wèn)題:

          1)將某一指向白色對(duì)象的引用寫(xiě)入黑色對(duì)象

          2)從灰色對(duì)象出發(fā),最終到達(dá)該白色對(duì)象的所有路徑都被破壞


          9、增量更新和起始快照

          為了解決三色標(biāo)記算法的漏標(biāo)問(wèn)題,產(chǎn)生了兩種比較著名的解決方案:增量更新和起始快照,CMS 和 G1 就是采用了這兩種解決方案,CMS 使用的增量更新,G1使用的起始快照。

          漏標(biāo)問(wèn)題的出現(xiàn)必須同時(shí)滿足上述的兩個(gè)條件,因此解決辦法只需破壞兩個(gè)條件之一即可。

          1)增量更新(Incremental update)

          使用寫(xiě)屏障(write barrier)攔截所有新插入的引用關(guān)系,將其記錄下來(lái),最后以這些引用關(guān)系的源頭作為根,重新掃描一遍即可解決漏標(biāo)問(wèn)題。

          增量更新破壞的是條件1,當(dāng)插入黑色對(duì)象到白色對(duì)象的引用時(shí),寫(xiě)屏障會(huì)記錄下該引用,后續(xù)重新掃描。

          以上面的漏標(biāo)為例,就是攔截步驟1:X.b=Y.a,記錄下X,然后重新掃描對(duì)象X。

          2)起始快照(SATB,snapshot at the begin)

          使用寫(xiě)屏障攔截所有刪除的引用關(guān)系,將其記錄下來(lái),然后將被刪除的引用關(guān)系所指向的對(duì)象會(huì)被當(dāng)作存活對(duì)象(非白色),重新掃描該對(duì)象。

          SATB 抽象的說(shuō)就是在一次GC開(kāi)始時(shí)刻是存活的對(duì)象,則在本次GC中會(huì)被當(dāng)做存對(duì)象,此時(shí)的對(duì)象形成一個(gè)邏輯“快照”,這也是起始快照名字的由來(lái)。

          起始快照破壞的是條件2,當(dāng)?shù)桨咨珜?duì)象的引用斷開(kāi)時(shí),寫(xiě)屏障會(huì)記錄下該引用,將該對(duì)象當(dāng)作存活對(duì)象,后續(xù)繼續(xù)掃描該對(duì)象的引用。

          上面的漏標(biāo)為例,就是攔截步驟2Y.a=null,將Z作為存活對(duì)象,然后重新掃描對(duì)象Z


          10、CMS中的 Final Remark(重新標(biāo)記)階段?較慢,怎么分析和解決?

          CMS 的整個(gè)垃圾回收過(guò)程中只有2個(gè)階段是 stop the world,一個(gè)是初始標(biāo)記,一個(gè)是重新標(biāo)記,初始標(biāo)記只標(biāo)記GC Roots直達(dá)的對(duì)象,因此一般不會(huì)耗時(shí)太久,而重新標(biāo)記出現(xiàn)耗時(shí)久的現(xiàn)象則比較多見(jiàn),通常如果CMS GC較慢,大多都是重新標(biāo)記階段較慢導(dǎo)致的。

          Final Remark 階段比較慢,比較常見(jiàn)的原因是在并發(fā)處理階段引用關(guān)系變化很頻繁,導(dǎo)致 dirty card 很多、年輕代對(duì)象很多。

          比較常見(jiàn)的做法可以在 Final Remark 階段前進(jìn)行一次 YGC,這樣年輕代的剩余待標(biāo)記對(duì)象會(huì)下降很多,被視為GC Root 的對(duì)象數(shù)量驟減, Final Remark 的工作量就少了很多。

          // 在remark之前嘗試進(jìn)行清理,默認(rèn)值為false -XX:+CMSScavengeBeforeRemark

          通常增加 -XX:+CMSScavengeBeforeRemark 都能解決問(wèn)題,但是如果優(yōu)化后還是耗時(shí)嚴(yán)重,則需要進(jìn)一步看具體是哪個(gè)小階段耗時(shí)嚴(yán)重。

          Final Remark 具體包含了若干個(gè)小階段:weak refs processing、class unloading、scrub string table等,從日志里可以看出來(lái)每個(gè)小階段的耗時(shí),根據(jù)耗時(shí)的階段再進(jìn)行針對(duì)性的分析,可以查閱源碼或者查閱相關(guān)資料來(lái)幫助分析。

          以比較常見(jiàn)的 weak refs processing 為例:

          這邊的 weak refs 不是單指 WeakReference,而是包括了:SoftReference、WeakReference、FinalReference、PhantomReference、JNI Weak Reference,這邊應(yīng)該是除了強(qiáng)引用外的所有引用都被歸類為 weak 了。

          因此,我們首先添加以下配置,打印出GC期間引用相關(guān)更詳細(xì)的日志。

          // 打印GC的詳細(xì)信息-XX:+PrintGCDetails // 打印在GC期間處理引用對(duì)象的時(shí)間(僅在PrintGCDetails時(shí)啟用) -XX:+PrintReferenceGC

          然后根據(jù)每個(gè)引用的耗時(shí),定位出耗時(shí)嚴(yán)重的引用類型,然后查看項(xiàng)目中是否存在對(duì)該引用類型不合理的使用。

          另外一種比較簡(jiǎn)單粗暴的辦法是可以通過(guò)增加引用的并行處理來(lái)嘗試解決,通常會(huì)有不錯(cuò)的效果。

           // 啟用并行引用處理,默認(rèn)值為false-XX:+ParallelRefProcEnabled

          而如果是 scrub string table 階段耗時(shí),則可以分析項(xiàng)目中是否存在不合理的使用 interned string,其他的也類似。


          最后

          我是囧輝,一個(gè)堅(jiān)持分享原創(chuàng)技術(shù)干貨的程序員


          推薦閱讀

          Java 基礎(chǔ)高頻面試題(2021年最新版)

          Java 集合框架高頻面試題(2021年最新版)

          面試必問(wèn)的 Spring,你懂了嗎?

          面試必問(wèn)的 MySQL,你懂了嗎?


          最近我將面試:阿里、字節(jié)、美團(tuán)、快手、拼多多等大廠的高頻面試整理出來(lái),并按大廠的標(biāo)準(zhǔn)給出自己的解析。

          群里有不少同學(xué)看完拿下了阿里、美團(tuán)等大廠 Offer,希望能助你一臂之力,早日拿下大廠 Offer。

          獲取方式:關(guān)注公眾號(hào)回復(fù)【面試】即可領(lǐng)取,更多大廠面試真題解析 PDF 整理中。

          b29a45ccf46b263f9a06d8a0129e3a64.webp

          瀏覽 72
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄片在现免费观看 | 蜜乳一区| 91丨国产丨白浆 | 啊啊啊啊被操逼了好爽视频免费 | 青青草免费视 |