1. 引言
Go 語言自帶垃圾回收(Garbage Collection, GC),讓開發者從手動管理內存的繁重任務中解脫出來。Go 的 GC 以其低延遲和并發性而聞名,其目標是在不長時間暫停(Stop The World, STW)整個程序的情況下完成大部分回收工作。
本文將深入探討 Go GC 的核心算法——并發三色標記清除法,以及其關鍵技術——寫屏障(Write Barrier)。
2. GC 的目標與挑戰
目標:回收堆上不再被使用的對象(垃圾),并將內存返還給分配器。
挑戰:如何在不影響程序正常運行(即低 STW 時間)的前提下,準確、高效地完成回收?這需要在“mutator”(用戶 Goroutine,會修改對象引用關系)和“collector”(GC Goroutine)之間進行精妙的協調。
3. 核心算法:并發三色標記-清除 (Tri-color Mark-and-Sweep)
Go GC 采用的是三色標記清除算法,它將堆上的對象分為三種顏色:
白色 (White):對象的初始狀態,代表可能是垃圾。在 GC 周期結束時,所有仍然是白色的對象都將被回收。
灰色 (Grey):對象本身已被標記為存活,但其引用的其他對象(其子對象)還沒有被掃描。灰色對象是待處理任務的集合。
黑色 (Black):對象本身和其引用的所有子對象都已被掃描,是確認的存活對象。
GC 流程分為四個主要階段:
Mark Setup (STW):
這是一個短暫的 STW 階段(通常在微秒級別)。
主要任務是開啟寫屏障 (Write Barrier),并準備標記工作。
將所有全局變量和每個 Goroutine 棧上的對象(根對象)放入灰色集合。
Marking (Concurrent):
這是 GC 的主要工作階段,與用戶 Goroutine 并發執行。
GC Goroutine 會從灰色集合中取出一個對象,將其標記為黑色。
然后掃描該對象的所有指針字段,將其引用的所有白色對象標記為灰色,并放入灰色集合。
這個過程會一直持續,直到灰色集合為空。
Mark Termination (STW):
這是另一個短暫的 STW 階段。
主要任務是處理一些在并發標記階段中被寫屏障捕獲的、可能被遺漏的指針修改,并關閉寫屏障。
Sweeping (Concurrent):
此階段也與用戶 Goroutine 并發執行。
GC 會遍歷堆中的所有
mspan
,回收所有仍然是白色對象的內存塊,并將其返還給內存分配器。
4. 關鍵技術:寫屏障 (Write Barrier)
在并發標記階段,如果用戶 Goroutine(mutator)修改了對象的引用關系,可能會破壞三色標記的不變性,導致本應存活的對象被錯誤回收。
危險場景:一個黑色對象引用了一個白色對象,同時該白色對象的所有其他灰色父引用被移除了。如果不加干預,這個白色對象將永遠不會被掃描,最終被當成垃圾回收。
黑色對象 -> 白色對象
為了防止這種情況,Go 引入了寫屏障。寫屏障是編譯器插入的一小段代碼,它會“攔截”所有在堆上的指針寫操作。
混合寫屏障 (Hybrid Write Barrier, Go 1.8+): Go 的混合寫屏障結合了兩種屏障的優點,其核心思想是:
它保護的是白色對象:不允許黑色對象直接引用白色對象。
工作機制:當
*slot = ptr
(一個指針寫操作)發生時,如果ptr
指向一個白色對象,寫屏障會將ptr
指向的對象涂成灰色。
通過這種方式,任何可能被黑色對象引用的白色對象都會被“拯救”回來,加入灰色集合,從而保證了 GC 的正確性。
5. GC 的觸發時機
GC 主要由以下條件觸發:
內存分配閾值 (
GOGC
): 當自上次 GC 以來新分配的內存達到一個閾值時,會自動觸發新的 GC。這個閾值由環境變量GOGC
控制(默認為 100),表示當堆大小增長 100% 時觸發。定時觸發:
runtime.sysmon
線程會定期檢查,如果距離上次 GC 超過一定時間(默認為 2 分鐘),會強制觸發一次 GC。手動觸發: 開發者可以調用
runtime.GC()
來手動觸發一次 GC。
6. 總結
Go GC 是一個低延遲、高并發的垃圾回收系統。它通過三色標記清除算法實現了大部分工作的并發執行,通過短暫的 STW 完成必要的同步,并通過混合寫屏障技術保證了在用戶 Goroutine 并發修改對象引用時的正確性。這一系列精巧的設計,是 Go 能夠勝任高并發、低延遲服務場景的重要保障。