Golang 垃圾回收-三色標記清除演算法

phpdi發表於2020-06-18

垃圾回收

垃圾回收是釋放掉那些不再使用的記憶體空間的過程.

golang GC演算法演變過程

版本 GC演算法
v1.1 STW(stop the word)
v1.3 Mark STW,Sweep(標記清除)
v1.5 三色標記
v1.8 hybrid write barrier(三色標記基礎上加入寫屏障)

標記清除演算法(mark and sweep)

主要包含兩個步驟:

1.找出不可達物件,然後做上標記
2.回收標記好的物件

mark and sweep 演算法在執行的時候,需要程式暫停,即stop the world

標記清除演算法存在的問題

  • stop the world 程式暫停,即程式會出現卡頓
  • 標記需要掃描整個堆(heap)
  • 清楚資料會產生heap碎片

三色標記清除演算法

三色標記清除演算法背後的首要原則就是它把堆中的物件根據它們的顏色分到不同集合裡面,顏色是根據演算法進行標記的

  • 黑色集合 指標指向白色集合。
  • 白色集合中的物件允許有指標指向黑色集合,白色集合中的物件就是垃圾回收的候選物件。
  • 灰色集合可能會有指標指向白色集合裡的物件。

寫屏障

每次堆中的指標被修改寫屏障都會去執行。如果堆中物件的指標被修改,就意味著那個物件現在是可觸達的,寫屏障會把它標記為灰色並把它放到灰色集合中。
修改器 執行寫屏障,從而保證黑色集合中沒有任何元素的指標去指向白色集合中的元素。
寫屏障直觀作用有兩個:
1.process新生成的記憶體物件會被直接標記成灰色
2.位於黑色集合中的記憶體物件引用了一個白色集合中的物件,寫屏障會將白色集合的這個物件標記為灰色

三色標記過程

1.首先:程式建立的物件都標記為白色。

2.gc開始:掃描所有可到達的物件,標記為灰色

3.從灰色物件中找到其引用物件標記為灰色,把灰色物件本身標記為黑色

4.監視物件中的記憶體修改,並持續上一步的操作,直到灰色標記的物件不存在

5.此時,gc回收白色物件。

6.最後,將所有黑色物件變為白色,並重復以上所有過程。

gc和使用者邏輯如何並行操作

標記-清除(mark and sweep)演算法的STW(stop the world)操作,就是runtime把所有的執行緒全部凍結掉,所有的執行緒全部凍結意味著使用者邏輯是暫停的。這樣所有的物件都不會被修改了,這時候去掃描是絕對安全的。
Go如何減短這個過程呢?標記-清除(mark and sweep)演算法包含兩部分邏輯:標記和清除。
我們知道Golang三色標記法中最後只剩下的黑白兩種物件,黑色物件是程式恢復後接著使用的物件,如果不碰觸黑色物件,只清除白色的物件,肯定不會影響程式邏輯。所以:清除操作和使用者邏輯可以併發。
標記操作和使用者邏輯也是併發的,使用者邏輯會時常生成物件或者改變物件的引用,那麼標記和使用者邏輯如何併發呢?
process新生成物件的時候,GC該如何操作呢?不會亂嗎?
我們看如下圖,在此狀態下:process程式又新生成了一個物件,我們設想會變成這樣:

但是這樣顯然是不對的,因為按照三色標記法的步驟,這樣新生成的物件A最後會被清除掉,這樣會影響程式邏輯。
Golang為了解決這個問題,引入了寫屏障這個機制。
寫屏障:該屏障之前的寫操作和之後的寫操作相比,先被系統其它元件感知。
通俗的講:就是在gc跑的過程中,可以監控物件的記憶體修改,並對物件進行重新標記。(實際上也是超短暫的stw,然後對物件進行標記)
在上述情況中,新生成的物件,一律都標位灰色!

那麼,灰色或者黑色物件的引用改為白色物件的時候,Golang是該如何操作的?
看如下圖,一個黑色物件引用了曾經標記的白色物件。

這時候,寫屏障機制被觸發,向GC傳送訊號,GC重新掃描物件並標位灰色。

因此,gc一旦開始,無論是建立物件還是物件的引用改變,都會先變為灰色。

參考文章

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章