上篇說到超超從彙編的角度的解析了make和new的區別,下面來到了面試中常見的考點GC,Go的GC常常因為效能問題被業界所詬病,下面跟著超超來看看記憶體垃圾是如何產生的,以及Go從1.3到1.8在GC上做了哪些改進吧!
記憶體垃圾
面試官:你知道程式的垃圾是怎麼產生的嗎?
考點:Go記憶體管理
超超:程式在記憶體上被分為堆區、棧區、全域性資料區、程式碼段、資料區五個部分。對於C++等早期程式語言棧上的記憶體由編譯器管理回收,堆上的記憶體空間需要程式設計人員負責申請與釋放。在Go中棧上記憶體仍由編譯器負責管理回收,而堆上的記憶體由編譯器和垃圾收集器負責管理回收,給程式設計人員帶來了極大的便利性。
垃圾是指程式向堆疊申請的記憶體空間,隨著程式的執行已經不再使用這些記憶體空間,這時如果不釋放他們就會造成垃圾也就是記憶體洩漏。
例如下面這段程式
1package main 2 3//假設每個人都擁有自己都一部手機 4type Person struct { 5 phone *Phone 6} 7 8type Phone struct { 9 money int10}1112func main() {13 //定義一個Person為超超14 chao := new(Person)1516 //超超一開始用的是iphone1217 iphone := &Phone{money: 6599}18 chao.phone = iphone1920 //華為推出了鴻蒙,於是超超果斷入了一部mate4021 huawei := &Phone{money: 5899}22 chao.phone = huawei2324}
隨著超超將手機從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用的舒服,面試好難呀??️
未完待續~
歡迎關注公眾號「Golang面試寶典」檢視文章最新進展!
本作品採用《CC 協議》,轉載必須註明作者和本文連結