一文搞懂 go gc 垃圾回收原理

什麼是垃圾回收

我們在程序中定義一個變量,會在內存中開闢相應內存空間進行存儲,當不需要此變量後,需要手動銷燬此對象,並釋放內存。而這種對不再使用的內存資源進行自動回收的功能即爲垃圾回收(Garbage Collection,縮寫爲 GC),是一種自動內存管理機制

如何識別垃圾

引用計數算法 (reference counting)

引用計數通過在對象上增加自己被引用的次數,被其他對象引用時加 1,引用自己的對象被回收時減 1,引用數爲 0 的對象即爲可以被回收的對象,這種算法在內存比較緊張和實時性比較高的系統中使用比較廣泛,如 php,Python 等。

優點:

  1. 方式簡單,回收速度快。

缺點:

  1. 需要額外的空間存放計數。

  2. 無法處理循環引用 (如 a.b=b; b.a=a)。

  3. 頻繁更新引用計數降低了性能。

追蹤式回收算法 (Tracing)

追蹤式算法 (可達性分析) 的核心思想是判斷一個對象是否可達,如果這個對象一旦不可達就可以立刻被 GC 回收了,那麼我們怎麼判斷一個對象是否可達呢?

第一步從根節點開始找出所有的全局變量和當前函數棧裏的變量,標記爲可達。第二步,從已經標記的數據開始,進一步標記它們可訪問的變量,以此類推,專業術語叫傳遞閉包。當追蹤結束時,沒有被打上標記的對象就被判定是不可觸達。

優點:

  1. 解決了循環引用的問題

  2. 佔用的空間少了

和引用計數法相比,有以下缺點:

  1. 無法立刻識別出垃圾對象,需要依賴 GC 線程

  2. 算法在標記時必須暫停整個程序,即 STW(stop the world),否則其他線程有可能會修改對象的狀態從而回收不該回收的對象

如何清理垃圾

標記清除算法 (Mark Sweep)

標記清除算法是最常見的垃圾收集算法,標記清除收集器是跟蹤式垃圾收集器,其執行過程可以分成標記 (Mark) 和清除 (Sweep) 兩個階段:

  1. 標記階段:暫停應用程序的執行,從根對象觸發查找並標記堆中所有存活的對象;

  2. 清除階段:遍歷堆中的全部對象,回收未被標記的垃圾對象並將回收的內存加入空閒鏈表,恢復應用程序的執行;

優點:

  1. 實現簡單。

缺點:

  1. 執行期間需要把整個程序完全暫停,不能異步的進行垃圾回收。

  2. 容易產生大量不連續的內存隨便,碎片太多可能會導致後續沒有足夠的連續內存分配給較大的對象,從而提前觸發新的一次垃圾收集動作。

標記複製算法

它把內存空間劃分爲兩個相等的區域,每次只使用其中一個區域。在垃圾收集時,遍歷當前使用的區域,把存活對象複製到另一個區域中,最後將當前使用的區域的可回收對象進行回收。

實現:

  1. 首先這個算法會把對分成兩塊,一塊是 From、一塊是 To

  2. 對象只會在 From 上生成,發生 GC 之後會找到所有的存活對象,然後將其複製到 To 區,然後整體回收 From 區。

優點:

  1. 不用進行大量垃圾對象的掃描:標記複製算法需要從GC-root對象出發,將可達的對象複製到另外一塊內存後直接清理當前這塊內存即可。

  2. 解決了內存碎片問題,防止分配大空間對象是提前 gc 的問題。

缺點:

  1. 複製成本問題:在可達對象佔用內存高的時候,複製成本會很高。

  2. 內存利用率低:相當於可利用的內存僅有一半。

標記壓縮算法

在標記可回收的對象後將所有存活的對象壓縮到內存的一端,使他們緊湊地排列在一起,然後對邊界以外的內存進行回收,回收後,已用和未用的內存都各自一邊。

優點:

  1. 避免了內存碎片化的問題。

  2. 適合老年代算法,老年代對象存活率高的情況下,標記整理算法由於不需要複製對象,效率更高。

缺點:

  1. 整理過程複雜:需要多次遍歷內存,導致 STW 時間比標記清除算法高。

設計原理

三色標記算法

爲了解決原始標記清除算法帶來的長時間 STW, Go 從 v1.5 版本實現了基於三色標記清除的併發垃圾收集器,在不暫停程序的情況下即可完成對象的可達性分析,三色標記算法將程序中的對象分成白色、黑色和灰色三類:

三色標記法屬於增量式 GC 算法,回收器首先將所有對象標記成白色,然後從 gc root 出發,逐步把所有可達的對象變成灰色再到黑色,最終所有的白色對象都是不可達對象。

具體實現:

優點:

缺點:

三色標記法存在併發性問題,

三色不變性

想要在併發或者增量的標記算法中保證正確性,我們需要達成一下兩種三色不變性中的任意一種。

屏障技術

垃圾收集中的屏障技術更像是一個鉤子方法,它是在用戶程序讀取對象、創建新對象以及更新對象指針時執行的一段代碼,根據操作類型的不同,我們可以將它們分成讀屏障和寫屏障兩種,因爲讀屏障需要在讀操作中加入代碼片段,對用戶程序的性能影響很大,所以變成語言往往都會採用寫屏障保證三色不變性。

插入寫屏障

當一個對象引用另外一個對象時,將另外一個對象標記爲灰色,以此滿足強三色不變性,不會存在黑色對象引用白色對象。

刪除寫屏障

在灰色對象刪除對白色對象的引用時,將白色對象置爲灰色,其實就是快照保存舊的引用關係,這叫 STAB(snapshot-at-the-beginning), 以此滿足弱三色不變性。

混合寫屏障

v1.8 版本之前,運行時會使用插入寫屏障保證強三色不變性;

在 v1.8 中,組合插入寫屏障和刪除寫屏障構成了混合寫屏障,保證弱三色不變性;該寫屏障會將覆蓋的對象標記成灰色 (刪除寫屏障) 並在當前棧沒有掃描時將新對象也標記成灰色(插入寫屏障):

寫屏障會將被覆蓋的指針和新指針都標記成灰色,而所有新建的對象都會被直接標記成黑色。

執行週期

Go 語言的垃圾收集可以分成清除終止、標記、標記終止和清除四個不同階段:

  1. 清理終止階段

  2. 暫停程序,所有的處理器在這時會進入安全點 (safe point);

  3. 如果當前垃圾收集循環是強制觸發的,我們還需要處理還未清理的內存管理單元;

  4. 標記階段

  5. 將狀態切換至_GCmark、開啓寫屏障、用戶程序協助 (Mutator Assists) 並將根對象入隊;

  6. 恢復執行程序,標記進程和用於協助的用戶程序會開始併發標記內存中的對象,寫屏障會將被覆蓋的指針和新指針都標記成灰色,而所有新創建的對象都會被直接標記成黑色;

  7. 開始掃描根對象,包括所有Goroutine的棧、全局對象以及不在堆中的運行時數據結構,掃描Goroutine棧期間會暫停當前處理器;

  8. 依次處理灰色隊列中的對象,將對象標記成黑色並將它們指向的對象標記成灰色;

  9. 使用分佈式的終止算法檢查剩餘的工作,發現標記階段完成後進入標記終止階段;

  10. 標記終止階段

  11. 暫停程序、將狀態切換至_GCmarktermination 並關閉輔助標記的用戶程序;

  12. 清理處理器上的線程緩存;

  13. 清理階段

  14. 將狀態切換至_GCoff 開始清理階段、初始化清理狀態並關閉寫屏障;

  15. 恢復用戶程序,所有新創建的對象會標記成白色;

  16. 後臺併發清理所有的內存管理單元,當Goroutine申請新的內存管理單元時就會觸發清理;

GC 觸發時機

當滿足觸發垃圾收集的基本條件:允許垃圾收集、程序沒有崩潰並且沒有處於垃圾循環;

注:運行時會通過如下所示的runtime.gcTrigger.test方法決定是否需要觸發垃圾收集,該方法會根據三種不同方式觸發進行不同的檢查。

func (t gcTrigger) test() bool {
 if !memstats.enablegc || panicking != 0 || gcphase != _GCoff {
  return false
 }
 switch t.kind {
 case gcTriggerHeap:
  return memstats.heap_live >= memstats.gc_trigger
 case gcTriggerTime:
  if gcpercent < 0 {
   return false
  }
  lastgc := int64(atomic.Load64(&memstats.last_gc_nanotime))
  return lastgc != 0 && t.now-lastgc > forcegcperiod
 case gcTriggerCycle:
  return int32(t.n-work.cycles) > 0
 }
 return true
}

GC 調優

減少堆內存的分配是最好的優化方式。比如合理重複利用對象;避免stringbyte[]之間的轉化等,兩者發生轉換的時候,底層數據結構會進行復制,因此導致 gc 效率會變低,少量使用+連接string,Go 裏面string是最基礎的類型,是一個只讀類型,針對他的每一個操作都會創建一個新的string,如果是少量小文本拼接,用“+”就好,如果是大量小文本拼接,用strings.Join; 如果是大量大文本拼接,用bytes.Buffer

優化努力的方向:

轉自:

https://juejin.cn/post/7111515970669117447

Go 開發大全

參與維護一個非常全面的 Go 開源技術資源庫。日常分享 Go, 雲原生、k8s、Docker 和微服務方面的技術文章和行業動態。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/a1gtHznuYeLVuNPVPYGCwg