圖解Golang垃圾回收機(jī)制!
上篇說到超超從匯編的角度的解析了make和new的區(qū)別,下面來到了面試中常見的考點(diǎn)GC,Go的GC常常因?yàn)樾阅軉栴}被業(yè)界所詬病,下面跟著超超來看看內(nèi)存垃圾是如何產(chǎn)生的,以及Go從1.3到1.8在GC上做了哪些改進(jìn)吧!
面試官:你知道程序的垃圾是怎么產(chǎn)生的嗎?
考點(diǎn):Go內(nèi)存管理
超超:程序在內(nèi)存上被分為堆區(qū)、棧區(qū)、全局?jǐn)?shù)據(jù)區(qū)、代碼段、數(shù)據(jù)區(qū)五個(gè)部分。對(duì)于C++等早期編程語言棧上的內(nèi)存由編譯器管理回收,堆上的內(nèi)存空間需要編程人員負(fù)責(zé)申請(qǐng)與釋放。在Go中棧上內(nèi)存仍由編譯器負(fù)責(zé)管理回收,而堆上的內(nèi)存由編譯器和垃圾收集器負(fù)責(zé)管理回收,給編程人員帶來了極大的便利性。

垃圾是指程序向堆棧申請(qǐng)的內(nèi)存空間,隨著程序的運(yùn)行已經(jīng)不再使用這些內(nèi)存空間,這時(shí)如果不釋放他們就會(huì)造成垃圾也就是內(nèi)存泄漏。
例如下面這段程序
1package main
2
3//假設(shè)每個(gè)人都擁有自己都一部手機(jī)
4type Person struct {
5 phone *Phone
6}
7
8type Phone struct {
9 money int
10}
11
12func main() {
13 //定義一個(gè)Person為超超
14 chao := new(Person)
15
16 //超超一開始用的是iphone12
17 iphone := &Phone{money: 6599}
18 chao.phone = iphone
19
20 //華為推出了鴻蒙,于是超超果斷入了一部mate40
21 huawei := &Phone{money: 5899}
22 chao.phone = huawei
23
24}
隨著超超將手機(jī)從iPhone換成了華為,phone所指向的內(nèi)存空間就變成了垃圾,這時(shí)就需要對(duì)phone指向的內(nèi)存空間進(jìn)行回收,否則就變成了內(nèi)存泄漏。

考點(diǎn):GC的實(shí)現(xiàn)
超超:那我從Go1.3開始說起吧,
Go1.3使用的是標(biāo)記清除法,分下面四步進(jìn)行
進(jìn)行STW(stop the worl即暫停程序業(yè)務(wù)邏輯),然后從main函數(shù)開始找到不可達(dá)的內(nèi)存占用和可達(dá)的內(nèi)存占用
開始標(biāo)記,程序找出可達(dá)內(nèi)存占用并做標(biāo)記
標(biāo)記結(jié)束清除未標(biāo)記的內(nèi)存占用
結(jié)束STW停止暫停,讓程序繼續(xù)運(yùn)行,循環(huán)該過程直到main生命周期結(jié)束

一開始的做法是將垃圾清理結(jié)束時(shí)才停止STW,后來優(yōu)化了方案將清理垃圾放到了STW之后,與程序運(yùn)行同時(shí)進(jìn)行,這樣做減小了STW的時(shí)長。但是STW會(huì)暫停用戶邏輯對(duì)程序的性能影響是非常大的,這種粒度的STW對(duì)于性能較高的程序還是無法接受,因此Go1.5采用了三色標(biāo)記法優(yōu)化了STW。
Go1.5三色標(biāo)記法
三色標(biāo)記算法將程序中的對(duì)象分成白色、黑色和灰色三類。白色對(duì)象表示暫無對(duì)象引用的潛在垃圾,其內(nèi)存可能會(huì)被垃圾收集器回收;灰色對(duì)象表示活躍的對(duì)象,黑色到白色的中間狀態(tài),因?yàn)榇嬖谥赶虬咨珜?duì)象的外部指針,垃圾收集器會(huì)掃描這些對(duì)象的子對(duì)象;黑色對(duì)象表示活躍的對(duì)象,包括不存在引用外部指針的對(duì)象以及從根對(duì)象可達(dá)的對(duì)象。

三色標(biāo)記法分五步進(jìn)行
將所有對(duì)象標(biāo)記為白色
從根節(jié)點(diǎn)集合出發(fā),將第一次遍歷到的節(jié)點(diǎn)標(biāo)記為灰色放入集合列表中
遍歷灰色集合,將灰色節(jié)點(diǎn)遍歷到的白色節(jié)點(diǎn)標(biāo)記為灰色,并把灰色節(jié)點(diǎn)標(biāo)記為黑色
循環(huán)這個(gè)過程
直到灰色節(jié)點(diǎn)集合為空,回收所有的白色節(jié)點(diǎn)


這種方法看似很好,但是將GC和程序會(huì)放一起執(zhí)行,會(huì)因?yàn)閏pu的調(diào)度出現(xiàn)下面這種情況,導(dǎo)致被引用的對(duì)象3會(huì)被垃圾回收掉,從而出現(xiàn)錯(cuò)誤。

分析bug的根源所在,主要是因?yàn)槌绦蛟谶\(yùn)行過程中出現(xiàn)了下面?zhèn)z種情況
一個(gè)白色對(duì)象被黑色對(duì)象引用
灰色對(duì)象與它之間的可達(dá)關(guān)系的白色對(duì)象遭到破壞
因此在此基礎(chǔ)上拓展出了倆種方法,強(qiáng)三色不變式和弱三色不變式
強(qiáng)三色不變式:不允許黑色對(duì)象引用白色對(duì)象
弱三色不變式:黑色對(duì)象可以引用白色,白色對(duì)象存在其他灰色對(duì)象對(duì)他的引用,或者他的鏈路上存在灰色對(duì)象
為了實(shí)現(xiàn)這倆種不變式的設(shè)計(jì)思想,從而引出了屏障機(jī)制,即在程序的執(zhí)行過程中加一個(gè)判斷機(jī)制,滿足判斷機(jī)制則執(zhí)行回調(diào)函數(shù)。

屏障機(jī)制分為插入屏障和刪除屏障,插入屏障實(shí)現(xiàn)的是強(qiáng)三色不變式,刪除屏障則實(shí)現(xiàn)了弱三色不變式。值得注意的是為了保證棧的運(yùn)行效率,屏障只對(duì)堆上的內(nèi)存對(duì)象啟用,棧上的內(nèi)存會(huì)在GC結(jié)束后啟用STW重新掃描。
插入屏障:對(duì)象被引用時(shí)觸發(fā)的機(jī)制,當(dāng)白色對(duì)象被黑色對(duì)象引用時(shí),白色對(duì)象被標(biāo)記為灰色(棧上對(duì)象無插入屏障)。

缺點(diǎn)在于:如果對(duì)象1在棧上新創(chuàng)建了一個(gè)對(duì)象6,由于棧沒有屏障機(jī)制,所以對(duì)象6仍為白色節(jié)點(diǎn)會(huì)被回收

所以棧在GC迭代結(jié)束時(shí)(沒有灰色節(jié)點(diǎn)),會(huì)對(duì)棧執(zhí)行STW,重新進(jìn)行掃描清除白色節(jié)點(diǎn)。(STW時(shí)間為10-100ms)
刪除屏障:對(duì)象被刪除時(shí)觸發(fā)的機(jī)制。如果灰色對(duì)象引用的白色對(duì)象被刪除時(shí),那么白色對(duì)象會(huì)被標(biāo)記為灰色。

缺點(diǎn):這種做法回收精度較低,一個(gè)對(duì)象即使被刪除仍可以活過這一輪再下一輪被回收。(如果對(duì)象4沒有引用對(duì)象3,此時(shí)對(duì)象3應(yīng)該作為垃圾被回收,但是對(duì)象3卻要等到下一輪GC才會(huì)被回收)

同樣也存在對(duì)棧的二次掃描影響程序的效率。
Go1.8 三色標(biāo)記+混合寫屏障
基于插入寫屏障和刪除寫屏障在結(jié)束時(shí)需要STW來重新掃描棧,所帶來的性能瓶頸,Go在1.8引入了混合寫屏障的方式實(shí)現(xiàn)了弱三色不變式的設(shè)計(jì)方式,混合寫屏障分下面四步
GC開始時(shí)將棧上可達(dá)對(duì)象全部標(biāo)記為黑色(不需要二次掃描,無需STW)
GC期間,任何棧上創(chuàng)建的新對(duì)象均為黑色
被刪除引用的對(duì)象標(biāo)記為灰色
被添加引用的對(duì)象標(biāo)記為灰色
下面為混合寫屏障過程

面試官:這個(gè)GC什么時(shí)候會(huì)被觸發(fā)呢?
考點(diǎn):GC細(xì)節(jié)
超超:觸發(fā)GC有倆個(gè)條件,一是堆內(nèi)存的分配達(dá)到控制器計(jì)算的觸發(fā)堆大小,初始大小環(huán)境變量GOGC,之后堆內(nèi)存達(dá)到上一次垃圾收集的 2 倍時(shí)才會(huì)觸發(fā)GC。二是如果一定時(shí)間內(nèi)沒有觸發(fā),就會(huì)觸發(fā)新的循環(huán),該觸發(fā)條件由runtime.forcegcperiod變量控制,默認(rèn)為 2 分鐘。
面試官:說到這那我們?cè)倭囊幌耇CMalloc算法吧。
超超:好的(自動(dòng)GC用的舒服,面試好難呀??????
未完待續(xù)~

如果你有什么問題想問超超,歡迎添加我的微信,進(jìn)讀者群和超超一起討論呀!
- Go 專欄|說說方法
- Go 專欄|錯(cuò)誤處理:defer,panic 和 recover
- 推薦三個(gè)實(shí)用的 Go 開發(fā)工具
- Go 專欄|函數(shù)那些事
- Go 專欄|流程控制,一網(wǎng)打盡
