JVM(十一)三色標記


前言:

所有的垃圾回收算法都要經歷標記階段。如果GC線程在標記的時候暫停所有用戶線程(STW),那就沒三色標記什么事了。但是這樣會有一個問題,用戶線程需要等到GC線程標記完才能運行,給用戶的感覺就是很卡,用戶體驗很差。

現在主流的垃圾收集器都支持並發標記。什么是並發標記呢?就是標記的時候不暫停或少暫停用戶線程,一起運行。這樣就會出現標記對象的過程中又有新的對象產生,或者標記對象過程中有改變對象引用的操作,對象間的引用可能發生變化多標漏標的情況就有可能發生。那這些情況標記過程中怎么處理呢?這就設計到標記算法了。

標記算法就是標記出那些對象是可以回收的,然后再執行回收操作。

三色標記,是把對象分成三種不同的顏色來表示不同的狀態。來表示是否可以進行回收。

     從GC Root對象開始掃描訪問。

  • 白色:尚未訪問過。
  • 黑色:本對象已訪問過,而且本對象 引用到 的其他對象 也全部訪問過了。標記過程中新產生的對象也是黑色的。
  • 灰色:本對象已訪問過,但是本對象 引用到 的其他對象 尚未全部訪問完。全部訪問后,會轉換為黑色。

     

     

假設現在有白、灰、黑三個集合(表示當前對象的顏色),其遍歷訪問過程為:

  1. 初始時,所有對象都在 【白色集合】中;
  2. 將GC Roots 直接引用到的對象 挪到 【灰色集合】中;
  3. 從灰色集合中獲取對象:
    3.1. 將本對象 引用到的 其他對象 全部挪到 【灰色集合】中;
    3.2. 將本對象 挪到 【黑色集合】里面。
  4. 重復步驟3,直至【灰色集合】為空時結束。
  5. 結束后,仍在【白色集合】的對象即為GC Roots 不可達,可以進行回收。

多標-浮動垃圾

假設已經遍歷到E(變為灰色了),此時應用執行了 objD.fieldE = null :

 

 

此刻之后,對象E/F/G是“應該”被回收的。然而因為E已經變為灰色了,其仍會被當作存活對象繼續遍歷下去。最終的結果是:這部分對象仍會被標記為存活,即本輪GC不會回收這部分內存

這部分本應該回收 但是 沒有回收到的內存,被稱之為“浮動垃圾”。浮動垃圾並不會影響應用程序的正確性,只是需要等到下一輪垃圾回收中才被清除。

另外,針對並發標記開始后的新對象,通常的做法是直接全部當成黑色,本輪不會進行清除。這部分對象期間可能會變為垃圾,這也算是浮動垃圾的一部分。

漏標-讀寫屏障

假設GC線程已經遍歷到E(變為灰色了),此時應用線程先執行了:
var G = objE.fieldG; 
objE.fieldG = null;  // 灰色E 斷開引用 白色G 
objD.fieldG = G;  // 黑色D 引用 白色G
此時切回GC線程繼續跑,因為 E已經沒有對G的引用了,所以不會將G放到灰色集合;盡管因為D重新引用了G,但因為 D已經是黑色了,不會再重新做遍歷處理。
最終導致的結果是:G會一直停留在白色集合中, 最后被當作垃圾進行清除。這直接 影響到了應用程序的正確性,是不可接受的。


不難分析,漏標只有 同時滿足以下兩個條件時才會發生:
條件一:灰色對象 斷開了 白色對象的引用(直接或間接的引用);即灰色對象 原來成員變量的引用 發生了變化。
條件二:黑色對象 重新引用了 該白色對象;即黑色對象 成員變量增加了 新的引用。
 從代碼的角度看:
var G = objE.fieldG; // 1.讀
objE.fieldG = null;  // 2.寫
objD.fieldG = G;     // 3.寫
  1. 讀取 對象E的成員變量fieldG的引用值,即對象G;
  2. 對象E 往其成員變量fieldG,寫入 null值。
  3. 對象D 往其成員變量fieldG,寫入 對象G ;
我們只要在上面這三步中的任意一步中做一些“手腳”, 將對象G記錄起來,然后作為灰色對象再進行遍歷即可。比如放到一個特定的集合,等初始的GC Roots遍歷完(並發標記),該集合的對象 遍歷即可(重新標記)。
重新標記是需要STW的,因為應用程序一直在跑的話,該集合可能會一直增加新的對象,導致永遠都跑不完。當然,並發標記期間也可以將該集合中的大部分先跑了,從而縮短重新標記STW的時間,這個是優化問題了。
寫屏障用於攔截第二和第三步;而讀屏障則是攔截第一步。
它們的攔截的目的很簡單:就是在讀寫前后,將對象G給記錄下來

上面漏標的情況,由於G會被回收,會導致程序過程中使用會有問題,為了避免這個問題,有兩種方法解決這個問題:

增量更新:

在標記程序運行過程中發生了引用鏈的變動,通過寫屏障將這個變動記錄下來,比如對象A對D建立新的引用時,將D放入一個OopMap中,作為灰色對象,並發標記結束后對這個OopMap進行遍歷,就可以避免漏標的情況。解決條件二。

原始快照:SATB

 

這種方式解決的是條件一,帶來的結果是依然能夠標記到D,具體做法如下: 對象B的引用關系變動的時候,即給B對象中的某個屬性賦值時,將之前的引用關系記錄下來。 標記的時候,掃描舊的對象圖,這個舊的對象圖即原始快照。

 

參考文章:https://www.jianshu.com/p/12544c0ad5c1

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM