序言
?垃圾回收(Garbage Collection,簡稱 GC)機制 是一種自動內存管理技術,主要用于在程序運行時自動識別并釋放不再使用的內存空間,防止內存泄漏和不必要的資源浪費。這篇文章讓我們來看一下 Go 語言的垃圾回收機制是如何設計的吧。
一、為什么需要垃圾回收?
?熟悉 C/C++ 編程的同學肯定清楚自己手動管理內存的手段:
- 分配內存:
malloc, new
- 釋放內存:
free, delete
對待內存管理一定要小心謹慎,不然稍有疏漏就會引起以下嚴重的問題:
- 忘記釋放內存 → 內存泄漏
- 多次釋放同一塊內存 → 崩潰或未定義行為
- 訪問已釋放的內存 → 懸空指針
所以說垃圾回收機制大大的提高了內存管理的下限,但是同時這是以一定的性能開銷換取的。
二、Go 垃圾回收機制
?我們現在看到的機制都是比較完善的,但是很多時候我們不明白他為什么要這么做,有什么好處?這是因為我們站在巨人的肩膀上,機制是在使用中不斷完善的。所以看事情不能只看現在,明白他大致來時候的路是必要的。
2.1 標記-清掃式垃圾回收(Mark-Sweep GC)
2.1.1 回收流程
?首先當前執行的邏輯暫停(也稱為 Stop-The-World STW
),然后標記所有存活對象。從根對象(比如全局變量、當前棧中的局部變量等)開始遍歷所有被引用的對象,并將對象標記為存活狀態:
現在標記的動作完成了,需要回收沒有被引用的空間了。遍歷整個堆的空間,然后對于沒有被標記的對象,釋放其內存:
最后便是恢復程序的執行了,可以看出剛開始的機制還是比較簡單的。
2.1.2 Mark-Sweep 的缺點
?STW
時間長降低了程序的執行的效率。如果當前的程序對于空間的申請和釋放的操作比較頻繁時,執行時的卡頓感會愈發的強烈,因為 STW
這段時間程序是被阻塞的,無法正常運行。
?內存碎片化嚴重。清掃后會產生很多小塊的空閑內存,可能導致大對象無法分配,降低了內存的利用率。
?掃描整個堆。如果當前堆比較大的話,也會拉長 STW
的時間。
這個方式是在 Go V1.4 之前使用的,現在被替換了,但是作為一個引子還是不錯的。
2.2 三色并發垃圾回收(Tri-color Concurrent GC )
2.2.1 回收流程
?首先在每次創建新的對象的時候將該對象標記為 白色
:
現在觸發 GC 了,從根節點遍歷,將該節點 root
指向的堆對象從 white 表
放入 grey 表
,由于這里只有一個根對象,所以這里只需要處理 a
:
根上的對象遍歷完成了,現在遍歷 grey 表
將節點 a
指向的對象也全放入 grey 表
,同時將 a
放入 black 表
表示他的可達對象處理完成了:
之后重復第二部操作直至 grey 表
的數據為空:
到最后我們發現 white 表
只剩下了一個不可達對象 f
,這個就是需要回收的空間。縱觀整個過程,其實就是一個廣度遍歷來查找不可達對象的過程。
?比較現有的兩個機制,后者一個很大優點就是 不需要遍歷整個堆來查找不可達對象,因為最開始的時候就記錄了創建的每一個對象。但是我們這里好像少了些什么,不需要 STW
嗎?
2.2.2 假設沒有 STW
?就比如 e
其實是一個可達對象的,但是由于在執行回收的過程中當前的程序也在正常的執行,讓 d
和 e
斷開連接并讓一個新的根節點(比如局部變量)指向 e
:
因為對根節點的遍歷只在最初執行一遍,后續不會再遍歷了導致錯誤地判定 e
為不可達對象釋放該空間,這不就錯誤地釋放空間了嗎(也稱為 漏標
)。
?最直接的方式就是在回收的過程中加上 STW
,但是這個方式的弊端上面也說過了。那怎么辦呢?減少 STW
的時間。
2.2.3 屏障機制
1. 強弱三色不變式
?由于程序的執行和垃圾回收的過程是并發的,就導致了錯誤地回收了某些還需要繼續使用的對象。為了避免這種情況,引入了 三色不變式。
?強三色不變式,所有黑對象不能直接或間接引用白對象。也就是說:如果一個對象已經是黑色,它不能指向任何未被標記的白色對象。
?弱色不變式。所有白對象可以被黑色對象引用,但是這個白色對象必須存在著其他灰色對象對他的引用。
2. 插入寫屏障
滿足:強三色不變式
操作:當一個黑色對象引用一個白色對象之前,先將該白色對象修改為灰色對象,在建立引用:
并且這里還有一個機制是 插入寫屏障只作用于堆對象。因為棧上的變量變更比較頻繁, 如果一變更我們就去執行插入寫屏障會非常的耽誤時間。作為補償,會在整體三色標記清除之后,專門對棧上的空間執行次三色標記掃描并加上 STW
保護。
3. 刪除寫屏障
滿足:弱三色不變式
操作:當一個白色對象被上游刪除引用時,會將將自己修改為灰色對象:
這種方式其實也是延遲回收策略,當真正想刪除該對象時,這一輪他會存活下來,但是下一輪肯定會被帶走。
2.3.4 Tri-color Concurrent 的缺點
?這兩種方式任選一種即可解決漏標的問題,Go V1.8 及以前使用的是刪除寫屏障。該種方式的缺點是:
- 回收精度偏低。本次 GC 過程中需要刪除的對象會在下一輪清除
而插入寫屏障的缺點也不小:
- 會在結束時掃描整個棧,并且伴隨著
STW
那是否可以取長補短互相融合呢?
2.3 混合寫屏障機制(Hybrid Write Barrier)
?將棧上的對象掃描之后全部標記為 黑色,期間任何新增的對象標記為 灰色,任何被刪除的對象也標記為 灰色。這樣節省了掃描整個棧并伴隨的 STW
帶來的性能消耗。
三、總結
?現在縱觀大體的發展路線,你是否可以理解:混合寫屏障(Hybrid Write Barrier)是一種改進型寫屏障機制,它結合了 刪除寫屏障 和 插入寫屏障 的優點,在并發三色標記中有效地防止漏標問題,并顯著減少了 STW 時間。