JVM 十一 三色標記

2022-06-16 17:33:12 字數 2753 閱讀 6853

前言:

所有的垃圾**演算法都要經歷標記階段。如果gc執行緒在標記的時候暫停所有使用者執行緒(stw),那就沒三色標記什麼事了。但是這樣會有乙個問題,使用者執行緒需要等到gc執行緒標記完才能執行,給使用者的感覺就是很卡,使用者體驗很差。

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

標記演算法就是標記出那些物件是可以**的,然後再執行**操作。

從gc root物件開始掃瞄訪問。

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

初始時,所有物件都在 【白色集合】中;

將gc roots 直接引用到的物件 挪到 【灰色集合】中;

從灰色集合中獲取物件:

3.1. 將本物件 引用到的 其他物件 全部挪到 【灰色集合】中;

3.2. 將本物件 挪到 【黑色集合】裡面。

重複步驟3,直至【灰色集合】為空時結束。

結束後,仍在【白色集合】的物件即為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.寫

讀取 物件e的成員變數fieldg的引用值,即物件g;

物件e 往其成員變數fieldg,寫入 null值。

物件d 往其成員變數fieldg,寫入 物件g ;

我們只要在上面這三步中的任意一步中做一些「手腳」,將物件g記錄起來,然後作為灰色物件再進行遍歷即可。比如放到乙個特定的集合,等初始的gc roots遍歷完(併發標記),該集合的物件 遍歷即可(重新標記)。

重新標記是需要stw的,因為應用程式一直在跑的話,該集合可能會一直增加新的物件,導致永遠都跑不完。當然,併發標記期間也可以將該集合中的大部分先跑了,從而縮短重新標記stw的時間,這個是優化問題了。

寫屏障用於攔截第二和第三步;而讀屏障則是攔截第一步。

它們的攔截的目的很簡單:就是在讀寫前後,將物件g給記錄下來

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

增量更新:

在標記程式執行過程中發生了引用鏈的變動,通過寫屏障將這個變動記錄下來,比如物件a對d建立新的引用時,將d放入乙個oopmap中,作為灰色物件,併發標記結束後對這個oopmap進行遍歷,就可以避免漏標的情況。解決條件二。

原始快照:satb

這種方式解決的是條件一,帶來的結果是依然能夠標記到d,具體做法如下:

物件b的引用關係變動的時候,即給b物件中的某個屬性賦值時,將之前的引用關係記錄下來。

標記的時候,掃瞄舊的物件圖,這個舊的物件圖即原始快照。

參考文章:

JVM 學習筆記(三)

選擇對應版本鏈結 tools visual gc 首先我們啟動本地工程,不停地往記憶體中新增物件,如下 restcontroller public class heapcontroller 使用的是springboot的工程,啟動後訪問 localhost 8080 heap 這樣是程式不停地往記憶...

JVM學習(三) 垃圾收集

1 垃圾收集 garbage collection,gc 需要考慮以下3件事 1 哪些記憶體需要 what 2 什麼時候 when 3 如何 how 2 判斷堆記憶體是否需要 主要是判斷物件的引用是否還存在,主要有以下策略 1 引用計數演算法 每個物件含有乙個引用計數器,有引用 1,引用失效則 1,...

問十一 JVM調優常用配置引數有哪些?

例如 xms20m xmx20m xss256k jvm配置 xx比x的穩定性更差,並且版本更新不會進行通知和說明。1 xms s為strating,表示堆記憶體起始大小 2 xmx x為max,表示最大的堆記憶體 一般來說 xms和 xmx的設定為相同大小,因為當heap自動擴容時,會發生記憶體抖...