Go的三色標記GC

三色標記

三色標記的原理以下:
整個進程空間裏申請每一個對象佔據的內存能夠視爲一個圖, 初始狀態下每一個內存對象都是白色標記,先stop the world,將掃描任務做爲多個併發的goroutine當即入隊給調度器,進而被CPU處理,第一輪先掃描全部可達的內存對象,標記爲灰色放入隊列;第二輪能夠恢復start the world,將第一步隊列中的對象引用的對象置爲灰色加入隊列,一個對象引用的全部對象都置灰並加入隊列後,這個對象才能置爲黑色並從隊列之中取出。循環往復,最後隊列爲空時,整個圖剩下的白色內存空間即不可到達的對象,即沒有被引用的對象; 第三輪再次stop the world,將第二輪過程當中新增對象申請的內存進行標記(灰色),這裏使用了writebarrier(寫屏障)去記錄這些內存的身份;golang

整個gc的流程以下圖:併發

clipboard.png

注意到:
mark 有兩個過程。優化

首先從 root 開始遍歷,root 包括全局指針和 goroutine 棧上的指針,標記爲灰色。遍歷灰色隊列。spa

re-scan 全局指針和棧。由於 mark 和用戶程序是並行的,因此在上一步執行的時候可能會有新的對象分配,這個時候就須要經過寫屏障(write barrier)記錄下來。re-scan 再完成檢查一下。指針

Stop The World 有兩個過程。協程

第一個是 GC 將要開始的時候,這個時候主要是一些準備工做,好比 enable write barrier。對象

第二個過程就是上面提到的 re-scan 過程。若是這個時候沒有 stw,那麼 mark 將無休止。blog

mark完畢後start the world進行並行清理,對於並行清理,GC 初始化的時候就會啓動 bgsweep()這個協程並一直在後臺阻塞, 開始清理時將這個協程喚醒並給主M去作併發的sweep。隊列

內存管理都是基於 span 的,mheap_ 是一個全局的變量,全部分配的對象都會記錄在 mheap_ 中。在標記的時候,咱們只要找到對對象對應的 span 進行標記,清掃的時候掃描 span,沒有標記的 span 就能夠回收了。進程

另外:1.8之後的golang將第一步的stop the world 也取消了,這又是一次優化:

clipboard.png

寫屏障

關於寫屏障的用處 以下面的例子,這個例子修改自知乎上的一個問答,在此表示感謝:

GC前:
stack->a->b ; a爲棧中申請的對象,b爲堆中申請的對象,a對象中存在對b的引用;

stack->c ; c 也是棧中申請的對象。

stop the world, mark。 這裏a,c都會被標記爲灰色;b爲白色
start the world 反覆mark。
因爲是併發的mark,咱們假設c先被處理,c沒有引用其餘對象,因此直接置黑,從隊列中取出;此時c爲黑色,a爲灰色,b爲白色

假設這時用戶作了以下操做:

a=nil
new(d);
c->b; 即,將a中對b的引用置爲空(你也能夠理解爲將a中對其餘任何內存對象的引用都清空),隨即申請d對象,而後在c中增長對b的引用。

因爲c已是黑色,因此不會再去掃描他,那麼本次內存掃描就不可能找獲得b;而d對象因爲剛申請出來,尚未被引用,因此這裏只對a進行了mark:a:黑色,b:白色;c:黑色;d:白色

這時用戶又作了:
b->d; 因爲b沒法被掃描到,這裏顯然d也不會被掃描到。 這樣的情況會一直持續到這輪反覆mark結束(即灰色隊列爲空)。

stop the world, mark termination。 sweep。 整個GC結束, b,d的內存空間都是白色,因此在sweep時會被清理掉。如何避免這種誤清理呢?

寫屏障的功能就是在 c->b發生時,對b標記爲灰色,入隊, 以及在b->d發生時,對d標記爲灰色,入隊,這樣,在整個反覆mark階段結束時,咱們能確保這段時間新發生的對白色對象的內存引用操做都被處理到(變黑),b和d就不會被誤清理。寫屏障在第一次掃描完,標記入隊後,反覆標記時開啓寫屏障, sweep前將寫屏障關閉。

簡而言之,寫屏障的做用大體是: 能夠確保不會有對象A直接引用白色對象B(發生時將白色對象置灰)。這裏有個小細節,go 1.5中無論對象A是什麼顏色,只要他引用了對象B,就將B置灰。

相關文章
相關標籤/搜索