Golang GC(垃圾回收機制)


Golang GC

1.常見的垃圾回收機制

1.1 引用計數

對每個對象維護一個引用計數,當引用對象的對象被銷毀時,引用計數-1,如果引用計數為0,則進行垃圾回收

  • 優點:對象可以很快的被回收,不會出現內存耗盡或達到某個閥值時才回收。
  • 缺點:不能很好的處理循環引用,而且實時維護引用計數,有也一定的代價。
  • 代表語言:Python、PHP、Swift

1.2 標記-清除

從根變量開始遍歷所有引用的對象,引用的對象標記為"被引用",沒有被標記的進行回收。

  • 優點:解決了引用計數的缺點。
  • 缺點:需要STW,即要暫時停掉程序運行。
  • 代表語言:Golang(其采用三色標記法)

1.3 分代收集

按照對象生命周期長短划分不同的代空間,生命周期長的放入老年代,而短的放入新生代,不同代有不能的回收算法和回收頻率。

  • 優點:回收性能好
  • 缺點:算法復雜
  • 代表語言: JAVA

2. Golang的標記清除

如下圖所示,通過gcmarkBits位圖標記span的塊是否被引用。對應內存分配中的bitmap區。

2.1 三色標記

  • 灰色:對象已被標記,但這個對象包含的子對象未標記
  • 黑色:對象已被標記,且這個對象包含的子對象也已標記,gcmarkBits對應的位為1(該對象不會在本次GC中被清理)
  • 白色:對象未被標記,gcmarkBits對應的位為0(該對象將會在本次GC中被清理)

例如,當前內存中有A~F一共6個對象,根對象a,b本身為棧上分配的局部變量,根對象a、b分別引用了對象A、B, 而B對象又引用了對象D,則GC開始前各對象的狀態如下圖所示:

  1. 初始狀態下所有對象都是白色的。
  2. 接着開始掃描根對象a、b; 由於根對象引用了對象A、B,那么A、B變為灰色對象,接下來就開始分析灰色對象,分析A時,A沒有引用其他對象很快就轉入黑色,B引用了D,則B轉入黑色的同時還需要將D轉為灰色,進行接下來的分析。
  3. 灰色對象只有D,由於D沒有引用其他對象,所以D轉入黑色。標記過程結束
  4. 最終,黑色的對象會被保留下來,白色對象會被回收掉。

img

2.2 GC的觸發

  • 閾值:默認內存擴大一倍,啟動gc
  • 定期:默認2min觸發一次gc,src/runtime/proc.go:forcegcperiod
  • 手動:runtime.gc()

2.3 STW

stop the world是gc的最大性能問題,對於gc而言,需要停止所有的內存變化,即停止所有的goroutine,等待gc結束之后才恢復。

標記-清除(mark and sweep)算法的STW(stop the world)操作,就是runtime把所有的線程全部凍結掉,所有的線程全部凍結意味着用戶邏輯是暫停的。這樣所有的對象都不會被修改了,這時候去掃描是絕對安全的。

Go如何減短這個過程呢?標記-清除(mark and sweep)算法包含兩部分邏輯:標記和清除。

我們知道Golang三色標記法中最后只剩下的黑白兩種對象,黑色對象是程序恢復后接着使用的對象,如果不碰觸黑色對象,只清除白色的對象,肯定不會影響程序邏輯。所以: 清除操作和用戶邏輯可以並發。

標記操作和用戶邏輯也是並發的,用戶邏輯會時常生成對象或者改變對象的引用,那么標記和用戶邏輯如何並發呢?這里就讓說到golang的寫屏障了,我們在2.5中介紹。

2.4 GC流程

  1. Sweep Termination: 對未清掃的span進行清掃, 只有上一輪的GC的清掃工作完成才可以開始新一輪的GC
  2. Mark: 掃描所有根對象, 和根對象可以到達的所有對象, 標記它們不被回收
  3. Mark Termination: 完成標記工作, 重新掃描部分根對象(要求STW)
  4. Sweep: 按標記結果清掃span

目前整個GC流程會進行兩次STW(Stop The World), 第一次是Mark階段的開始, 第二次是Mark Termination階段.

  • 第一次STW會准備根對象的掃描, 啟動寫屏障(Write Barrier)和輔助GC(mutator assist).
  • 第二次STW會重新掃描部分根對象, 禁用寫屏障(Write Barrier)和輔助GC(mutator assist).

需要注意的是, 不是所有根對象的掃描都需要STW, 例如掃描棧上的對象只需要停止擁有該棧的G.
從go 1.9開始, 寫屏障的實現使用了Hybrid Write Barrier, 大幅減少了第二次STW的時間.

2.5 寫屏障

因為go支持並行GC, GC的掃描和go代碼可以同時運行,這樣帶來的問題是GC掃描的過程中go代碼有可能改變了對象的依賴樹。

例如開始掃描時發現根對象A和B,B擁有C的指針。

  1. GC先掃描A,A放入黑色
  2. B把C的指針交給A
  3. GC再掃描B,B放入黑色
  4. C在白色,會回收;但是A其實引用了C。

為了避免這個問題, go在GC的標記階段會啟用寫屏障(Write Barrier).

啟用了寫屏障(Write Barrier)后,

  1. GC先掃描A,A放入黑色
  2. B把C的指針交給A
  3. 由於A在黑色,所以C放入灰色
  4. C沒有子對象,放入黑色
  5. 掃描B,B沒有子對象,放入黑色

即使A可能會在稍后丟掉C, 那么C就在下一輪回收。

開啟寫屏障之后,當指針發生改變, GC會認為在這一輪的掃描中這個指針是存活的, 所以放入灰色


免責聲明!

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



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