三色標記算法原理詳述及CMS和G1如何解決其並發標記問題


三色標記算法是CMS和G1在並發標記階段都普遍采用的一種trace算法

首先,為什么要對對象進行標記?

因為tracing的過程中你必須對已經遍歷過、正在遍歷、還沒有遍歷到的對象進行區分,如果不進行區分的話,那你tracing有什么意義呢?每次某個垃圾回收線程重新獲得了cpu的時間分片,回來之后發現自己根本啥都不記得了,遍歷過哪些對象(是否是垃圾)已經全忘了,只能從頭重新tracing... 這是一個非常嚴重的問題!!!

 

因此為了有效的對是否遍歷過的對象的狀態進行區分,便有了這個三色標記的算法,其思想很簡單,那就是將我剛剛提到的對象的三種狀態用不同的顏色進行標記,如果這個對象及對象的引用對象全部都tracing完了,那么這時候我就可以將對象標記為黑色,而如果對象本身已經遍歷過了,但是對象所引用的那些孩子對象還沒有遍歷過,這時候我可以將對象標記為灰色,如果對象完全沒有遍歷過(對象本身 + 對象的引用孩子),那么此時我就可以將對象標記為白色,這樣一來,即使發生了線程切換,因為在對象頭(hotspot)里面存在着對象的顏色標記,仍然看可以從上次遍歷過的地方繼續遍歷,tracing就可以了

 

這個過程感覺有點類似於層序遍歷算法BFS,每次我tracing一層,然后如果孩子不為空的話,將孩子都入隊列,然后將本身標記為灰色,之后就不斷的從隊列中取孩子,如果孩子全部都遍歷完了,那么這時候就可以將原來的父節點,置為黑色,以后便可以不再找這個黑色父節點的麻煩了!

 

 

 剛剛我解釋的過程如果用一張圖來表示的話,大概就是這樣,三種顏色代表着tracing的三種 不同的狀態!

 

但是我們知道,不論是CMS還是G1的垃圾回收的過程中,並發標記都是和用戶線程一起進行的,這會帶來什么問題呢?

你一邊遍歷tracing,但是發現對象間的引用關系還在不斷的發生變化,這個過程是不是有點太扯了,讓垃圾收集器很難做啊!(真的太難了T T)

具體說的話會帶來哪些問題呢? 一共分為兩種case:

1.部分對象會存在遍歷不到的問題

 

 

 

 如上圖所示,B本來有個引用屬性,是引用了對象D B.d = D,但是隨着用戶線程的繼續進行,卻B到D的這個引用關系卻消失了, B.d = null 這時候勢必會沒有辦法遍歷到D這個對象了,我們仔細思考一下,對於D本身而言,沒有任何GC Roots對象可以遍歷到它,它本身就是一個垃圾對象,如果遍歷過程遍歷不到的話,它也會變成一個浮動垃圾,所以對於JVM來說的話從結果上看沒什么問題,D對象變成了浮動垃圾,計算這次GC干不掉它,下次GC也一定可以把它給干掉。

(順便插一句,這也就是為什么CMS不會真的等到堆空間快滿了的時候再去執行垃圾清理的過程,因為隨着用戶線程不斷變化引用關系,勢必會有浮動垃圾產生,這時候如果用戶線程不斷產生大量的對象到堆,浮動垃圾本次又無法清理,會導致堆空間立馬打滿,JVM只能產生STW來清理堆空間,對於用戶而言,就會看到程序假死)

 

2.產生last object remove的問題(很嚴重)

轉變為 -> 

 也就是原來B到D的引用消失了,但是D並沒有變成垃圾,而是被A重新引用上了這樣一個過程

可是對於虛擬機而言,這個過程是存在問題的,因為A已經變成了黑色,從三色標記算法的約定角度出發,是不會再去重新遍歷A的,而B會繼續遍歷,但是因為引用關系的改變,D也不會通過B再trace到了,這樣的話,D對於通過三色標記算法的trace而言是一個垃圾對象,會被垃圾回收器進行回收!

可是D因為存在着A到它的引用關系,它並不是一個垃圾,如果此時垃圾回收器清理了D,那么導致的問題就是程序執行到A並取其d屬性的時候就會發生空指針異常的問題!

 

上面就是三色標記算法,及其可能產生的兩個問題的總結

 

接下來補充一個知識點,那就是CMS和G1在進行並發標記的過程中,是如何解決上述我提到的last object remove的問題的呢?

 

對於CMS而言,它的解決方案非常的直覺,那就是利用write barrier當發生了類似於A->D這種重新引用的情況時,如果發現主動引用的對象此時已經是黑色了,那么這時候我就會重新將其變為灰色,這樣子就會重新發起對A對象的再trace的過程,理論上變不會漏掉其新增的屬性對象了;

但是這個過程仍然存在bug,看圖

 

 如上圖中文字標記的過程所示,m1線程將屬性1已經trace完了,然后m2的用戶線程此時將新的白色對象D指向了已經標記完的屬性1,然后m3線程此時將對象A標記為灰色

這個重新標記成灰色的過程其實沒啥用不知道大家發現了沒有,因為對於m1線程而言,其看A本身就是還是灰色的,2屬性還沒標記呢,然后m3線程因為發生了1屬性的重新指向,所以再次將A的顏色變為灰色,

根本就沒啥區別,當m1重新獲得執行權限的時候,它會繼續遍歷2屬性,當2屬性也trace完了之后,就把A對象標記為黑色了,問題還是沒有得到解決!

所以,最終CMS采用的終極解決方案那就是再重新標記的過程中,對於這種可能存在問題的對象,再執行一遍從頭trace的過程,這個過程是需要STW的!

 

 

那G1是怎么解決這個問題的呢?

G1不會從變更和改動入手去解決這個問題,而是追根溯源,采用了一種叫做SATB的方式來解決,所謂的SATB,就是snatshop at the begining的縮寫,看翻譯我們大概也能夠知道這種思路,那就是如果B對D的引用發生了變化的話,我們會將這條消失的引用關系鏈記錄到一個隊列里,當我們都trace完成之后,會從隊列中將這部分消失的關系,重新取出來,在繼續進行補充遍歷過程,這樣子便不會再發生剛剛說過的last object remove的問題啦!

 

PS:本文所用配圖來自mashibing老師的ppt中,如果侵權立馬刪除,才疏學淺,知識理解能力有限,如有錯誤,歡迎批評指正!


免責聲明!

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



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