圖解 Golang 垃圾回收機制!
Go 的 GC 常常因爲性能問題被業界所詬病,下面跟着超超來看看內存垃圾是如何產生的,以及 Go 從 1.3 到 1.8 在 GC 上做了哪些改進吧!
內存垃圾
面試官:你知道程序的垃圾是怎麼產生的嗎?
考點:Go 內存管理
超超:程序在內存上被分爲堆區、棧區、全局數據區、代碼段、數據區五個部分。對於 C++ 等早期編程語言棧上的內存由編譯器管理回收,堆上的內存空間需要編程人員負責申請與釋放。在 Go 中棧上內存仍由編譯器負責管理回收,而堆上的內存由編譯器和垃圾收集器負責管理回收,給編程人員帶來了極大的便利性。
垃圾是指程序向堆棧申請的內存空間,隨着程序的運行已經不再使用這些內存空間,這時如果不釋放他們就會造成垃圾也就是內存泄漏。
例如下面這段程序
package main
//假設每個人都擁有自己都一部手機
type Person struct {
phone *Phone
}
type Phone struct {
money int
}
func main() {
//定義一個Person爲超超
chao := new(Person)
//超超一開始用的是iphone12
iphone := &Phone{money: 6599}
chao.phone = iphone
//華爲推出了鴻蒙,於是超超果斷入了一部mate40
huawei := &Phone{money: 5899}
chao.phone = huawei
}
隨着超超將手機從 iPhone 換成了華爲,phone
所指向的內存空間就變成了垃圾,這時就需要對phone
指向的內存空間進行回收,否則就變成了內存泄漏。
Go 的垃圾回收機制
面試官:那你來給我說說 Go 語言是如何實現 GC 的吧!
考點:GC 的實現
超超:那我從 Go1.3 開始說起吧,
Go1.3 使用的是標記清除法,分下面四步進行
-
進行 STW(stop the worl 即暫停程序業務邏輯),然後從 main 函數開始找到不可達的內存佔用和可達的內存佔用
-
開始標記,程序找出可達內存佔用並做標記
-
標記結束清除未標記的內存佔用
-
結束 STW 停止暫停,讓程序繼續運行,循環該過程直到 main 生命週期結束
一開始的做法是將垃圾清理結束時才停止 STW,後來優化了方案將清理垃圾放到了 STW 之後,與程序運行同時進行,這樣做減小了 STW 的時長。但是 STW 會暫停用戶邏輯對程序的性能影響是非常大的,這種粒度的 STW 對於性能較高的程序還是無法接受,因此 Go1.5 採用了三色標記法優化了 STW。
Go1.5 三色標記法
三色標記算法將程序中的對象分成白色、黑色和灰色三類。白色對象表示暫無對象引用的潛在垃圾,其內存可能會被垃圾收集器回收;灰色對象表示活躍的對象,黑色到白色的中間狀態,因爲存在指向白色對象的外部指針,垃圾收集器會掃描這些對象的子對象;黑色對象表示活躍的對象,包括不存在引用外部指針的對象以及從根對象可達的對象。
三色標記法分五步進行
-
將所有對象標記爲白色
-
從根節點集合出發,將第一次遍歷到的節點標記爲灰色放入集合列表中
-
遍歷灰色集合,將灰色節點遍歷到的白色節點標記爲灰色,並把灰色節點標記爲黑色
-
循環這個過程
-
直到灰色節點集合爲空,回收所有的白色節點
這種方法看似很好,但是將 GC 和程序會放一起執行,會因爲 cpu 的調度出現下面這種情況,導致被引用的對象 3 會被垃圾回收掉,從而出現錯誤。
分析 bug 的根源所在,主要是因爲程序在運行過程中出現了下面倆種情況
-
一個白色對象被黑色對象引用
-
灰色對象與它之間的可達關係的白色對象遭到破壞
因此在此基礎上拓展出了倆種方法,強三色不變式和弱三色不變式
-
強三色不變式:不允許黑色對象引用白色對象
-
弱三色不變式:黑色對象可以引用白色,白色對象存在其他灰色對象對他的引用,或者他的鏈路上存在灰色對象
爲了實現這倆種不變式的設計思想,從而引出了屏障機制,即在程序的執行過程中加一個判斷機制,滿足判斷機制則執行回調函數。
屏障機制分爲插入屏障和刪除屏障,插入屏障實現的是強三色不變式,刪除屏障則實現了弱三色不變式。值得注意的是爲了保證棧的運行效率,屏障只對堆上的內存對象啓用,棧上的內存會在 GC 結束後啓用 STW 重新掃描。
插入屏障:對象被引用時觸發的機制,當白色對象被黑色對象引用時,白色對象被標記爲灰色(棧上對象無插入屏障)。
缺點在於:如果對象 1 在棧上新創建了一個對象 6,由於棧沒有屏障機制,所以對象 6 仍爲白色節點會被回收
所以棧在 GC 迭代結束時(沒有灰色節點),會對棧執行 STW,重新進行掃描清除白色節點。(STW 時間爲 10-100ms)
刪除屏障:對象被刪除時觸發的機制。如果灰色對象引用的白色對象被刪除時,那麼白色對象會被標記爲灰色。
缺點:這種做法回收精度較低,一個對象即使被刪除仍可以活過這一輪再下一輪被回收。(如果對象 4 沒有引用對象 3,此時對象 3 應該作爲垃圾被回收,但是對象 3 卻要等到下一輪 GC 纔會被回收)
同樣也存在對棧的二次掃描影響程序的效率。
Go1.8 三色標記 + 混合寫屏障
基於插入寫屏障和刪除寫屏障在結束時需要 STW 來重新掃描棧,所帶來的性能瓶頸,Go 在 1.8 引入了混合寫屏障的方式實現了弱三色不變式的設計方式,混合寫屏障分下面四步
-
GC 開始時將棧上可達對象全部標記爲黑色(不需要二次掃描,無需 STW)
-
GC 期間,任何棧上創建的新對象均爲黑色
-
被刪除引用的對象標記爲灰色
-
被添加引用的對象標記爲灰色
下面爲混合寫屏障過程
面試官:這個 GC 什麼時候會被觸發呢?
考點:GC 細節
超超:觸發 GC 有倆個條件,一是堆內存的分配達到控制器計算的觸發堆大小,初始大小環境變量 GOGC,之後堆內存達到上一次垃圾收集的 2 倍時纔會觸發 GC。二是如果一定時間內沒有觸發,就會觸發新的循環,該觸發條件由runtime.forcegcperiod
變量控制,默認爲 2 分鐘。
面試官:說到這那我們再聊一下 TCMalloc 算法吧。
超超:好的(自動 GC 用的舒服,面試好難呀😶🌫️
未完待續~
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/RSW0tAWsrILivqA7r-LlIA