0.1、索引
https://waterflow.link/articles/1664943418972
文中提到的垃圾回收演算法是基于go1.16之后的,讓我們直接進入正題吧,
1、什么時候需要垃圾回收?

Go 更喜歡在堆疊上分配記憶體,因此大多數記憶體分配最終都會在堆疊上, 這意味著 Go 每個 goroutine 都有一個堆疊,并且在可能的情況下,Go 會將變數分配給這個堆疊, Go 編譯器試圖通過執行逃逸分析來查看物件是否被外部變數參考, 如果編譯器可以確定一個變數的生命周期,它將被分配到一個堆疊中, 但是,如果變數的生命周期不明確,它將在堆上分配, 通常,如果 Go 程式有一個指向物件的指標,則該物件存盤在堆上, 看看這個示例代碼:
type myStruct struct {
value int
}
var testStruct = myStruct{value: 0}
func addTwoNumbers(a int, b int) int {
return a + b
}
func myFunction() {
testVar1 := 123
testVar2 := 456
testStruct.value = https://www.cnblogs.com/liuyuede123/p/addTwoNumbers(testVar1, testVar2)
}
func someOtherFunction() {
myFunction()
}
我們假設這是一個正在運行的程式的一部分,因為如果這是整個程式,Go 編譯器會通過將變數分配到堆疊中來優化它, 程式運行時:
- testStruct 被定義并放置在堆上的一個可用記憶體塊中,
- myFunction 在函式執行時被執行并分配一個堆疊, testVar1 和 testVar2 都存盤在此堆疊中,
- 當 addTwoNumbers 被呼叫時,一個新的堆疊幀被壓入堆疊中,并帶有兩個函式引數,
- 當 addTwoNumbers 完成執行時,它的結果回傳給 myFunction 并且 addTwoNumbers 的堆疊幀從堆疊中彈出,因為它不再需要了,
- 指向 testStruct 的指標被定為到包含它的堆上的位置,并且值欄位被更新,
- myFunction 退出并且為其創建的堆疊被清理, testStruct 的值會一直保留在堆上,直到發生垃圾回收,
testStruct 現在在堆上并且沒有分析,Go 運行時不知道是否仍然需要它, 為此,Go 依賴于垃圾回收器, 垃圾回收器有兩個關鍵部分,一個 mutator 和一個回收器, 回收器執行垃圾收集邏輯并找到應該釋放其記憶體的物件, mutator 執行應用程式代碼并將新物件分配給堆, 它還會在程式運行時更新堆上的現有物件,其中包括在不再需要某些物件時使其無法訪問,

2、垃圾回收器的實作
Go 的垃圾收集器是一個非分代并發、三色標記和清除垃圾回收器, 讓我們分解一下這些術語,
什么是分代:
由于“復制”演算法對于存活時間長,大容量的儲存物件需要耗費更多的移動時間,和存在儲存物件的存活時間的差異,需要程式將所擁有的記憶體空間分成若干磁區,并標記為年輕代空間和年老代空間,程式運行所需的存盤物件會先存放在年輕代磁區,年輕代磁區會較為頻密進行較為激進垃圾回收行為,每次回收完成幸存的存盤物件內的壽命計數器加一,當年輕代磁區存盤物件的壽命計數器達到一定閾值或存盤物件的占用空間超過一定閾值時,則被移動到年老代空間,年老代空間會較少運行垃圾回收行為,一般情況下,還有永久代的空間,用于涉及程式整個運行生命周期的物件存盤,例如運行代碼、資料常量等,該空間通常不進行垃圾回收的操作, 通過分代,存活在局限域,小容量,壽命短的存盤物件會被快速回收;存活在全域域,大容量,壽命長的存盤物件就較少被回收行為處理干擾,——維基百科
分代垃圾回收器專注于最近分配的物件, 但是,如前所述,編譯器優化允許 Go 編譯器將具有已知生命周期的物件分配給堆疊, 這意味著更少的物件將在堆上,因此更少的物件將被垃圾回收, 這意味著在 Go 中不需要分代垃圾回收器, 因此,Go 使用了非分代垃圾回收器, 并發意味著回收器與 mutator 執行緒同時運行, 因此,Go 使用非分代并發垃圾回收器, 標記和清除是垃圾回收器的型別,三色是用于實作它的演算法,
Go 通過幾個步驟實作了這一點:
1、開啟寫屏障
Go 通過一個名為 stop the world 的行程讓所有 goroutine 到達垃圾回收安全點, 這會暫時停止程式運行并打開寫屏障以維護堆上的資料完整性, 這通過允許 goroutine 和回收器同時運行來實作并發,

想要在并發或者增量的標記演算法中保證正確性,我們需要達成以下兩種三色不變性(Tri-color invariant)中的一種:
- 強三色不變性 — 黑色物件不會指向白色物件,只會指向灰色物件或者黑色物件;
- 弱三色不變性 — 黑色物件指向的白色物件必須包含一條從灰色物件經由多個白色物件的可達路徑;

一旦所有的 goroutine 都打開了寫屏障,Go 運行時就會starts the world并讓workers執行垃圾回收作業,
2、標記階段
標記是通過使用三色演算法實作的, 當標記開始時,根物件是灰色的,其他物件都是白色的, 根是所有其他堆物件的源物件,并作為運行程式的一部分被實體化, 垃圾回收器通過掃描堆疊、全域變數和堆指標開始標記以了解正在使用的內容, 掃描堆疊時,workers 停止 goroutine 并通過從根向下遍歷將所有找到的物件標記為灰色, 掃描完成恢復 goroutine,
三色標記的作業原理:
- 從灰色物件的集合中選擇一個灰色物件并將其標記成黑色;
- 將黑色物件指向的所有物件都標記成灰色,保證該物件和被該物件參考的物件都不會被回收;
- 重復上述兩個步驟直到物件圖中不存在灰色物件;
下圖是準備標記:

下圖為當有新物件生成時,因為開啟了寫屏障,會直接標記為黑色

下圖為根物件可達的物件都標記為黑色

3、清理階段
然后將灰色物件排入佇列以變為黑色,這表明它們仍在使用中, 一旦所有灰色物體都變成黑色,回收器將再次stop the world并清理所有不再需要的白色節點, 然后應用程式現在可以繼續運行,直到它需要再次清理更多記憶體,
下圖為STW然后清理白色物件

下圖為清理之后,恢復程式運行

一旦程式分配了與正在使用的記憶體成比例的額外記憶體,此程序將再次啟動, GOGC 環境變數決定了這一點,默認設定為 100, Go 源代碼將其描述為:
如果 GOGC=100 并且我們使用 4M,我們將在達到 8M 時再次進行 GC(此標記在 next_gc 變數中跟蹤), 這使 GC 成本與分配成本成線性比例, 調整 GOGC 只會改變線性常數(以及使用的額外記憶體量),
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/518915.html
標籤:Go
下一篇:golang中的字串
