1. 程式人生 > 其它 >併發可達性分析-三色標記法

併發可達性分析-三色標記法

在3.2節中曾經提到了當前主流程式語言的垃圾收集器基本上都是依靠可達性分析演算法來判定物件
是否存活的,可達性分析演算法理論上要求全過程都基於一個能保障一致性的快照中才能夠進行分析,
這意味著必須全程凍結使用者執行緒的執行。在根節點列舉(見3.4.1節)這個步驟中,由於GC Roots相比
起整個Java堆中全部的物件畢竟還算是極少數,且在各種優化技巧(如OopMap)的加持下,它帶來
的停頓已經是非常短暫且相對固定(不隨堆容量而增長)的了。可從GC Roots再繼續往下遍歷物件
圖,這一步驟的停頓時間就必定會與Java堆容量直接成正比例關係了:堆越大,儲存的物件越多,對
象圖結構越複雜,要標記更多物件而產生的停頓時間自然就更長,這聽起來是理所當然的事情。
要知道包含“標記”階段是所有追蹤式垃圾收集演算法的共同特徵,如果這個階段會隨著堆變大而等
比例增加停頓時間,其影響就會波及幾乎所有的垃圾收集器,同理可知,如果能夠削減這部分停頓時
間的話,那收益也將會是系統性的。
想解決或者降低使用者執行緒的停頓,就要先搞清楚為什麼必須在一個能保障一致性的快照上才能進
行物件圖的遍歷?為了能解釋清楚這個問題,我們引入三色標記(Tri-color Marking)[1]作為工具來輔
助推導,把遍歷物件圖過程中遇到的物件,按照“是否訪問過”這個條件標記成以下三種顏色:
·白色:表示物件尚未被垃圾收集器訪問過。顯然在可達性分析剛剛開始的階段,所有的物件都是
白色的,若在分析結束的階段,仍然是白色的物件,即代表不可達。
·黑色:表示物件已經被垃圾收集器訪問過,且這個物件的所有引用都已經掃描過。黑色的物件代
表已經掃描過,它是安全存活的,如果有其他物件引用指向了黑色物件,無須重新掃描一遍。黑色對
象不可能直接(不經過灰色物件)指向某個白色物件。
·灰色:表示物件已經被垃圾收集器訪問過,但這個物件上至少存在一個引用還沒有被掃描過。
關於可達性分析的掃描過程,讀者不妨發揮一下想象力,把它看作物件圖上一股以灰色為波峰的
波紋從黑向白推進的過程,如果使用者執行緒此時是凍結的,只有收集器執行緒在工作,那不會有任何問
題。但如果使用者執行緒與收集器是併發工作呢?收集器在物件圖上標記顏色,同時使用者執行緒在修改引用
關係——即修改物件圖的結構,這樣可能出現兩種後果。一種是把原本消亡的物件錯誤標記為存活,
這不是好事,但其實是可以容忍的,只不過產生了一點逃過本次收集的浮動垃圾而已,下次收集清理
掉就好。另一種是把原本存活的物件錯誤標記為已消亡,這就是非常致命的後果了,程式肯定會因此
發生錯誤,
下面表3-1演示了這樣的致命錯誤具體是如何產生的。


Wilson於1994年在理論上證明了,當且僅當以下兩個條件同時滿足時,會產生“物件消失”的問
題,即原本應該是黑色的物件被誤標為白色:
·賦值器插入了一條或多條從黑色物件到白色物件的新引用;
·賦值器刪除了全部從灰色物件到該白色物件的直接或間接引用。

因此,我們要解決併發掃描時的物件消失問題,只需破壞這兩個條件的任意一個即可。由此分別
產生了兩種解決方案:增量更新(Incremental Update)和原始快照(Snapshot At The Beginning,
SATB)。
增量更新要破壞的是第一個條件,當黑色物件插入新的指向白色物件的引用關係時,就將這個新
插入的引用記錄下來,等併發掃描結束之後,再將這些記錄過的引用關係中的黑色物件為根,重新掃
描一次。這可以簡化理解為,黑色物件一旦新插入了指向白色物件的引用之後,它就變回灰色物件
了。
原始快照要破壞的是第二個條件,當灰色物件要刪除指向白色物件的引用關係時,就將這個要刪
除的引用記錄下來,在併發掃描結束之後,再將這些記錄過的引用關係中的灰色物件為根,重新掃描
一次。這也可以簡化理解為,無論引用關係刪除與否,都會按照剛剛開始掃描那一刻的物件圖快照來
進行搜尋。
以上無論是對引用關係記錄的插入還是刪除,虛擬機器的記錄操作都是通過寫屏障實現的。在
HotSpot虛擬機器中,增量更新和原始快照這兩種解決方案都有實際應用,譬如,CMS是基於增量更新
來做併發標記的,G1、Shenandoah則是用原始快照來實現。
到這裡,筆者簡要介紹了HotSpot虛擬機器如何發起記憶體回收、如何加速記憶體回收,以及如何保證回
收正確性等問題,但是虛擬機器如何具體地進行記憶體回收動作仍然未涉及。因為記憶體回收如何進行是由
虛擬機器所採用哪一款垃圾收集器所決定的,而通常虛擬機器中往往有多種垃圾收集器,下面筆者將逐一
介紹HotSpot虛擬機器中出現過的垃圾收集器。