golang gc 原理和內存分配


更好的閱讀體驗建議點擊下方原文鏈接。
原文地址:http://maoqide.live/post/golang/golang-gc-memory-allocation/

關於 Golang GC 和內存管理相關的流程和原理的一些總結。

GC 流程

golang GC 采用基於標記-清除的三色標記法,下圖為 golang 一輪完整的 GC 的過程:

一輪完整的 GC,總是從 Off,如果不是 Off 狀態,則代表上一輪GC還未完成,如果這時修改指針的值,是直接修改的。

Stack scan: 收集根對象(全局變量和 goroutine 棧上的變量),該階段會開啟寫屏障(Write Barrier)。

Mark: 標記對象,直到標記完所有根對象和根對象可達對象。此時寫屏障會記錄所有指針的更改(通過 mutator)。

Mark Termination: 重新掃描部分全局變量和發生更改的棧變量,完成標記,該階段會STW(Stop The World),也是 gc 時造成 go 程序停頓的主要階段。

Sweep: 並發的清除未標記的對象。

三色標記

以上 Mark 階段,采用的是三色標記法,是傳統標記-清除算法的一種優化,主要思想是增加了一種中間狀態,即灰色對象,以減少 STW 時間。
三色標記將對象分為黑色、白色、灰色三種:

  • 黑色:已標記的對象,表示對象是根對象可達的。
  • 白色:未標記對象,gc開始時所有對象為白色,當gc結束時,如果仍為白色,說明對象不可達,在 sweep 階段會被清除。
  • 灰色:被黑色對象引用到的對象,但其引用的自對象還未被掃描,灰色為標記過程的中間狀態,當灰色對象全部被標記完成代表本次標記階段結束。

三色標記的主要過程即:

  1. 開始時所有對象為白色
  2. 將所有根對象標記為灰色,放入隊列
  3. 遍歷灰色對象,將其標記為黑色,並將他們引用的對象標記為灰色,放入隊列
  4. 重復步驟 3 持續遍歷灰色對象,直至隊列為空
  5. 此時只剩下黑色對象和白色對象,白色對象即為下一步需要清除的對象

STW

傳統的標記-清除算法,為了防止在標記過程中,對象引用發生變化,導致清除仍在使用的對象,需要 STW(Stop The World),這會造成程序的停頓。在三色標記的過程中,由於引入了灰色對象這一中間狀態,標記過程和用戶的 golang 代碼中可以並發執行,不需要 STW,這極大的減少了應用的停頓時間。
三色標記具體如何避免在標記過程中對象應用的改變呢,這里用到了寫屏障(Write Barrier)。

寫屏障

在 GC 的流程中,Stack scan 這一步驟,啟用了寫屏障。寫屏障的主要思想,是在標記的過程中,通過寫屏障記錄發生變化的指針,然后在 Mark termination 的 rescan 過程中,重新進行掃描,因為在這一步驟會 STW,所以在這一步驟完成后的白色對象,不會再被引用,可以直接清除。關於寫屏障具體原理和實現,這里不再展開。

GC觸發

golang 程序的執行過程中,如下幾種情況下會觸發 GC:

  • 主動觸發,用戶代碼中調用 runtime.GC 會主動觸發 GC
  • 默認每 2min 未產生 GC 時,golang 的守護協程 sysmon 會強制觸發 GC
  • 當 go 程序分配的內存增長超過閾值時,會觸發 GC

內存分配

golang 內存分配分為堆內存和棧內存。
棧:一般函數內部執行中聲明的變量,函數返回直接釋放,不會引起垃圾回收,對性能無影響。
堆:有引用到的內存空間,靠 GC 回收,會影響程序進程。

內存逃逸

逃逸分析是指由編譯器決定內存分配的位置,不需要程序員指定。即由編譯器決定新申請的對象會分配到堆上還是棧上。
逃逸分析場景:

  1. 指針逃逸
    go 將函數內定義的變量返回到函數外,會將本應分配到棧上的內存分配到堆上。
  2. 棧空間不足逃逸
    當棧空間不足或無法判斷當前切片長度時會將對象分配到堆上。
  3. 動態類型逃逸
    當函數參數為 interface 類型,編譯期間無法確定參數的具體類型,也可能會產生逃逸。

參考列表


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM