golang筆記——GC 原理

一绊起、GC觸發(fā)

  • 內存分配量達到閥值觸發(fā) GC
    每次內存分配時蚊伞,都會檢查當前內存分配量是否已達到閥值泼舱,如果達到閥值則立即啟動 GC:
    • 閥值 = 上次 GC 內存分配量 * 內存增長率
    • 內存增長率由環(huán)境變量 GOGC 控制躁绸,默認為 100裕循,即每當內存擴大一倍時啟動 GC
  • 定期觸發(fā) GC
    默認情況下,最長 2 分鐘净刮,由sysmon觸發(fā)一次 GC费韭,這個間隔在 src/runtime/proc.go:forcegcperiod 變量中被聲明
  • 手動觸發(fā)
    程序代碼中也可以使用 runtime.GC()來手動觸發(fā) GC。這主要用于 GC 性能測試和統(tǒng)計庭瑰。

二星持、 v1.3 標記-清除算法

1、 v1.3 之前

整體流程圖


具體步驟

  1. 啟動STW(Stop The World)弹灭,暫停程序業(yè)務邏輯


  2. 從根對象開始標記督暂,找出所有可達的對象,并做上標記穷吮。如下圖所示:


  3. 標記完成后逻翁,清除未標記的對象


  4. 停止STW,讓程序繼續(xù)運行捡鱼。然后循環(huán)重復這個過程八回,直到process程序生命周期結束。

缺點:

  • STW讓程序暫停驾诈,CPU全部用于垃圾回收缠诅,程序出現(xiàn)卡頓(重要問題)
  • 標記需要掃描整個heap
  • 清楚數(shù)據(jù)會產(chǎn)生heap碎片
2、 v1.3優(yōu)化Mark & Sweep

由于未被標記的不可達對象乍迄,基本不會再次被引用(由于程序中沒有對象擁有不可達對象的地址管引,所以難以再被其他對象引用)。因此闯两,在Mark標記完成后褥伴,就停止STW谅将,讓程序恢復運行,可以減少STW的時間重慢,同時也不會影響Sweep清除的正確性饥臂。

所以,go在v1.3版本做了簡單的優(yōu)化似踱,將STW的步驟提前, 減少STW暫停的時間范圍擅笔,同時并發(fā)執(zhí)行Sweep清除。如下所示:

優(yōu)化后的GC仍存在STW的問題屯援,Go V1.5版本使用了三色并發(fā)標記法來繼續(xù)優(yōu)化這個問題。

三念脯、v1.5 三色并發(fā)標記狞洋、插入寫屏障、刪除寫屏障

三色標記的一個明顯好處是能夠讓用戶程序和 mark 并發(fā)的進行

“三色”只是為了敘述上方便抽象出來的一種說法绿店,實際上對象并沒有顏色之分吉懊。這里的“三色”,對應了垃圾回收過程中對象的三種狀態(tài):

  • 黑色:已被回收器訪問到的對象假勿,其子對象也已被被回收器訪問到
  • 灰色:已被回收器訪問到的對象借嗽,但其可能仍存在子對象未被回收器訪問到。
  • 白色:未被回收器訪問到的對象(潛在的垃圾)转培,其內存可能會被垃圾收集器回收恶导。

1、三色并發(fā)標記--具體步驟:

  1. 初始將所有內存標記為白色浸须,將所有對象放入白色集合中


  2. 然后將 roots 加入worklist(進入worklist即被視為變成灰色)


  3. 從根節(jié)點開始遍歷所有對象惨寿,把遍歷到的對象從白色集合放入“灰色”集合。
    (本次遍歷只會對根節(jié)點下的子節(jié)點進行1次遍歷删窒,是非遞歸遍歷裂垦,僅遍歷1層)


  4. 遍歷灰色集合,將灰色對象引用的對象從白色集合放入灰色集合肌索,之后將此灰色對象放入黑色集合蕉拢。
    (這一次遍歷只掃描灰色對象,將灰色對象的第一層遍歷可抵達的對象由白色變?yōu)榛疑?將灰色對象標記為黑色诚亚,并將其從灰色標記表移動到黑色標記表)


  5. 重復第3步, 直到灰色標記表中無任何對象



  6. 回收所有的白色標記表的對象.


但是這里面可能會有很多并發(fā)流程均會被掃描晕换,執(zhí)行并發(fā)流程的內存可能相互依賴,為了在GC過程中保證數(shù)據(jù)的安全站宗,我們在開始三色標記之前就會加上STW届巩,在掃描確定黑白對象之后再放開STW。但是很明顯這樣的GC掃描的性能實在是太低了份乒。

2恕汇、沒有STW腕唧,帶來漏標問題

假設我們執(zhí)行三色并發(fā)標記時,不執(zhí)行STW瘾英。用戶程序枣接,有可能將1個灰色對象G下白色子對象W的引用,轉移給1個黑色對象B缺谴。在GC時會誤刪除對象W但惶,從而導致程序異常。詳見下圖:


由上圖可以看出湿蛔,有兩種情況膀曾,在三色標記法中,是不希望被發(fā)生的:
- 條件1: 一個白色對象被黑色對象引用(白色被掛在黑色下)
- 條件2: 灰色對象與它之間的可達關系的白色對象遭到破壞(灰色同時丟了該白色)

如果當以上兩個條件同時滿足時阳啥,就會出現(xiàn)對象丟失現(xiàn)象添谊!為了防止這種現(xiàn)象的發(fā)生,我們只要使用一種機制察迟,嘗試去破壞上面的兩個必要條件就可以了斩狱。這樣也可以避免STW帶來的資源浪費問題。

3扎瓶、屏障機制

破壞上面的兩個必要條件所踊,有兩種方式:

  • 強三色不變式:不允許黑色對象引用任何白色對象
  • 弱三色不變式:允許黑色對象引用白色對象,但該白色對象的可達路徑中必須存在灰色對象

在GC源碼中對應兩種屏障機制:“Dijkstra 插入寫屏障”概荷、“Yuasa 刪除寫屏障”秕岛。

  • Dijkstra 插入寫屏障
    具體操作:在A對象引用C對象的時候,C對象被標記為灰色误证。(將C掛在A下游瓣蛀,C必須被標記為灰色)

    滿足:強三色不變式. (不存在黑色對象引用白色對象的情況了, 因為白色會強制變成灰色)
    偽碼如下
// 添加下游對象
writePointer(slot, ptr):
    // 標記灰色(新下游對象ptr) 
    shade(ptr)
    // 當前下游對象slot = 新下游對象ptr
    *slot=ptr
  • Yuasa 刪除寫屏障
    具體操作:從對象B被刪除的對象C雷厂,如果對象C自身為灰色或者白色惋增,那么對象C被標記為灰色。

    滿足:弱三色不變式. (保護灰色對象到白色對象的路徑不會斷)
    偽碼如下
// 添加下游對象
writePointer(slot, ptr):
    // 如果當前對象是灰色或白色
    if ( isGrey(slot) || isWhite(slot) )
        // 標記灰色(當前下游對象ptr) 
        shade(*slot)
    // 當前下游對象slot = 新下游對象ptr
    *slot = ptr
  • 插入寫屏障與刪除寫屏障的缺點

    • 插入寫屏障:結束時需要STW來重新掃描棧改鲫,標記棧上引用的白色對象的存活诈皿。僅適用于堆(程序運行基本在棧中,存在大量變量聲明像棘、賦值及函數(shù)調用稽亏,若棧中使用插入寫屏障,將極大增加復雜度缕题、降低性能)
    • 刪除寫屏障:回收精度低截歉,GC開始時STW掃描堆棧來記錄初始快照,這個過程會保護開始時刻的所有存活對象烟零。僅適用于堆
  • 整體流程


    1. 初始化 GC 任務瘪松,包括開啟寫屏障(write barrier)和開啟輔助GC(mutator assist)咸作,統(tǒng)計 root對象的任務數(shù)量等,這個過程需要STW宵睦。
    2. 掃描所有 root 對象(全局指針记罚、goroutine(G) 棧上的指針(掃描對應 G棧時需停止該G)),將其加入灰色隊列壳嚎,并循環(huán)處理灰色隊列的對象桐智,直到灰色隊列為空,該過程后臺并行執(zhí)行
    3. 完成標記工作烟馅,重新掃描(re-scan)全局指針和棧说庭。因為 Mark是并行執(zhí)行的,且棧中不適用插入寫屏障郑趁,所以棧中可能會存在新的未掃描的對象刊驴。同時這個re-scan過程會執(zhí)行STW
    4. 按照標記結果回收所有的白色對象穿撮,該過程后臺并行執(zhí)行。

四痪欲、v1.8 混合寫屏障

Go V1.8版本引入了混合寫屏障機制(hybrid write barrier)悦穿,避免了對棧re-scan的過程,極大的減少了STW的時間业踢。結合了兩者的優(yōu)點栗柒。
整體流程

1. GC開始將棧上的可達對象全部掃描并標記為黑色(當前過程無需STW)
2. GC開始執(zhí)行標記操作,任何在堆\棧上創(chuàng)建的新對象知举,均為黑色瞬沦。
3. 標記結束,開始STW雇锡,重新掃描全局指針逛钻,不再rescan棧,并執(zhí)行其他相關操作
4. 關閉STW回收未標記對象锰提,調整下一次GC pacing
滿足:變形的弱三色不變式曙痘。
偽碼如下

// 添加下游對象
writePointer(slot, ptr):
    // 標記灰色(當前下游對象ptr) 
    shade(*slot)
    // 如果當前堆棧對象是黑色
    if current stack is grey:
        // 標記灰色(新下游對象ptr) 
        shade(ptr)
    // 當前下游對象slot = 新下游對象ptr
    *slot = ptr

通過以下場景,我們看下在v1.5和v1.8中的GC變化

v1.5 v1.8
堆對象A --x--> 堆對象B立肘,棧對象C ----> 堆對象B 堆中刪除寫屏障標記B為灰色 堆中刪除寫屏障標記B為灰色
堆對象A ----> 新堆對象B 堆中插入寫屏障標記B為灰色 堆中創(chuàng)建的新對象默認均為黑色
棧對象A --x--> 棧對象B边坤,棧對象C ----> 棧對象B rescan后,最終標記B為黑色 GC開始時谅年,已將B標記為黑色
棧對象A ----> 新棧對象B rescan后茧痒,最終標記B為黑色 棧中創(chuàng)建的新對象默認均為黑色
棧對象A ----> 新堆對象B rescan后,最終標記B為黑色 堆中創(chuàng)建的新對象默認均為黑色
棧對象A --x--> 棧對象B rescan后融蹂,最終被清除 GC開始時旺订,已將B標記為黑色弄企;等待下次GC清除

五、3個版本Mark&Sweep對比

  • GoV1.3 - 普通標記清除法耸峭,整體過程需要啟動STW桩蓉,效率極低。
  • GoV1.5 - 三色標記法劳闹, 堆空間啟動寫屏障院究,棧空間不啟動本涕,全部掃描之后业汰,需要重新掃描一次棧(需要STW),效率普通
  • GoV1.8 - 三色標記法菩颖,混合寫屏障機制样漆, 棧空間不啟動晦闰,堆空間啟動放祟。整個過程幾乎不需要STW,效率較高呻右。

六跪妥、GC Sweep


Go 提供2種方式來清理內存:

  1. 在后臺啟動一個 worker 等待清理內存,一個一個mspan 處理
    當開始運行程序時声滥,Go 將設置一個后臺運行的Worker(唯一的任務就是去清理內存)眉撵,它將進入睡眠狀態(tài)并等待內存段掃描
    當GC worker未清理完內存,但新一輪GC又開始了落塑。這時這個運行 GC 的 goroutine 就會在開始標記階段前去協(xié)助完成剩余的清理工作
  2. 當申請分配內存時候 lazy 觸發(fā)
    當應用程序 goroutine 嘗試在堆內存中分配新內存時纽疟,會觸發(fā)該操作。清理導致的延遲和吞吐量降低被分散到每次內存分配時憾赁。
    該方式屬于即時執(zhí)行污朽,由于被使用的內存段已經(jīng)被分發(fā)到每一個P 的本地緩存 mcache 中,很難追蹤首先清理哪些內存龙考,因此Go 會先將所有內存段移動到mcentral膘壶,讓本地緩存mcache 再次請求它們,去即時清理洲愤。即時掃描確保所有內存段都會得到清理(節(jié)省資源)颓芭,同時不會阻塞程序執(zhí)行

Referencs:
https://www.yuque.com/aceld/golang/zhzanb
https://www.cnblogs.com/zj420255586/p/14261834.html#12-%E6%A0%87%E8%AE%B0-%E6%B8%85%E9%99%A4
https://golang.design/under-the-hood/zh-cn/part2runtime/ch08gc/basic/
https://www.qycn.com/xzx/article/10803.html
https://www.ardanlabs.com/blog/2018/12/garbage-collection-in-go-part1-semantics.html
https://www.ardanlabs.com/blog/2018/12/garbage-collection-in-go-part2-semantics.html
https://www.ardanlabs.com/blog/2018/12/garbage-collection-in-go-part3-semantics.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市柬赐,隨后出現(xiàn)的幾起案子亡问,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件州藕,死亡現(xiàn)場離奇詭異束世,居然都是意外死亡,警方通過查閱死者的電腦和手機床玻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進店門毁涉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锈死,你說我怎么就攤上這事贫堰。” “怎么了待牵?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵其屏,是天一觀的道長。 經(jīng)常有香客問我缨该,道長偎行,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任贰拿,我火速辦了婚禮蛤袒,結果婚禮上,老公的妹妹穿的比我還像新娘膨更。我一直安慰自己妙真,他們只是感情好,可當我...
    茶點故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布询一。 她就那樣靜靜地躺著隐孽,像睡著了一般癌椿。 火紅的嫁衣襯著肌膚如雪健蕊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天踢俄,我揣著相機與錄音缩功,去河邊找鬼。 笑死都办,一個胖子當著我的面吹牛嫡锌,可吹牛的內容都是我干的。 我是一名探鬼主播琳钉,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼势木,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了歌懒?” 一聲冷哼從身側響起啦桌,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后甫男,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體且改,經(jīng)...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年板驳,在試婚紗的時候發(fā)現(xiàn)自己被綠了又跛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,435評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡若治,死狀恐怖慨蓝,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情直砂,我是刑警寧澤菌仁,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站静暂,受9級特大地震影響济丘,放射性物質發(fā)生泄漏。R本人自食惡果不足惜洽蛀,卻給世界環(huán)境...
    茶點故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一摹迷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧郊供,春花似錦峡碉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至疯淫,卻和暖如春地来,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背熙掺。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工未斑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人币绩。 一個月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓蜡秽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缆镣。 傳聞我的和親對象是個殘疾皇子芽突,可洞房花燭夜當晚...
    茶點故事閱讀 45,442評論 2 359

推薦閱讀更多精彩內容