GO語言的垃圾回收

GC 算法有四種:

  • 引用計數(reference counting)
  • 標記-清除(mark & sweep)
  • 節點複製(Copying Garbage Collection)
  • 分代收集(Generational Garbage Collection)。

 

引用計數(reference counting)

引用計數的思想:每個單元維護一個域,保存其它單元指向它的引用數量(類似有向圖的入度)。當引用數量爲0時,將其回收。引用計數是漸進式的,能夠將內存管理的開銷分佈到整個程序之中。C++ 的 share_ptr 使用的就是引用計算方法。

引用計數算法實現一般是把所有的單元放在一個單元池裏,比如類似 free list。這樣所有的單元就被串起來了,就可以進行引用計數了。新分配的單元計數值被設置爲 1(注意不是 0,因爲申請一般都說 ptr = new object 這種)。每次有一個指針被設爲指向該單元時,該單元的計數值加 1;而每次刪除某個指向它的指針時,它的計數值減 1。 當其引用計數爲 0 的時候,該單元會被進行回收。雖然這裏說的比較簡單,實現的時候還是有很多細節需要考慮,比如刪除某個單元的時候,那麼它指向的所有單元都需要對引用計數減 1。

優點

漸進式。內存管理與用戶程序的執行交織在一起,將 GC 的代價分散到整個程序。不像標記-清掃算法需要 STW (Stop The World,GC 的時候掛起用戶程序)。

算法易於實現。

內存單元能夠很快被回收。相比於其他垃圾回收算法,堆被耗盡或者達到某個閾值纔會進行垃圾回收。

缺點

原始的引用計數不能處理循環引用。大概這是被詬病最多的缺點了。不過針對這個問題,也除了很多解決方案,比如強引用等。

維護引用計數降低運行效率。內存單元的更新刪除等都需要維護相關的內存單元的引用計數,相比於一些追蹤式的垃圾回收算法並不需要這些代價。

單元池 free list 實現的話不是 cache-friendly 的,這樣會導致頻繁的 cache miss,降低程序運行效率。

 

標記-清除(mark & sweep)

標記-清除算法是第一種自動內存管理,基於追蹤的垃圾收集算法。算法思想在 70 年代就提出了,是一種非常古老的算法。內存單元並不會在變成垃圾立刻回收,而是保持不可達狀態,直到到達某個閾值或者固定時間長度。這個時候系統會掛起用戶程序,也就是 STW,轉而執行垃圾回收程序。 垃圾回收程序對所有的存活單元進行一次全局遍歷確定哪些單元可以回收。算法分兩個部分:標記(mark)和清除(sweep)。標記階段表明所有的存活單元,清除階段將垃圾單元回收。

標記-清除算法的優點也就是基於追蹤的垃圾回收算法具有的優點:避免了引用計數算法的缺點(不能處理循環引用,需要維護指針)。缺點也很明顯,需要 STW。

三色標記算法是對標記階段的改進,原理如下:

  • 起初所有對象都是白色。
  • 從根出發掃描所有可達對象,標記爲灰色,放入待處理隊列。
  • 從隊列取出灰色對象,將其引用對象標記爲灰色放入隊列,自身標記爲黑色。
  • 重複 3,直到灰色對象隊列爲空。此時白色對象即爲垃圾,進行回收。

三色法標記主要是第一部分是掃描所有對象進行三色標記,標記爲黑色、灰色和白色,標記完成後只有黑色和白色對象,黑色代表使用中對象,白色對象代表垃圾,灰色是白色過渡到黑色的中間臨時狀態,第二部分是清掃垃圾,即清理白色對象。

三色標記的一個明顯好處是能夠讓用戶程序和 mark 併發的進行.

爲什麼需要三色標記?

三色標記的目的,主要是利用Tracing GC(Tracing GC 是垃圾回收的一個大類,另外一個大類是引用計數)做增量式垃圾回收,降低最大暫停時間。原生Tracing GC只有黑色和白色,沒有中間的狀態,這就要求GC掃描過程必須一次性完成,得到最後的黑色和白色對象。在前面增量式GC中介紹到了,這種方式會存在較大的暫停時間。

三色標記增加了中間狀態灰色,增量式GC運行過程中,應用線程的運行可能改變了對象引用樹,只要讓黑色對象直接引用白色對象,GC就可以增量式的運行,減少停頓時間。

什麼是三色標記?

三色標記,通過字面意思我們就可以知道它由3種顏色組成:

  1. 黑色 Black:表示對象是可達的,即使用中的對象,黑色是已經被掃描的對象。

  2. 灰色 Gary:表示被黑色對象直接引用的對象,但還沒對它進行掃描。

  3. 白色 White:白色是對象的初始顏色,如果掃描完成後,對象依然還是白色的,說明此對象是垃圾對象。

三色標記規則:黑色不能指向白色對象。即黑色可以指向灰色,灰色可以指向白色。

三色標記法,主要流程如下:

  1. 初始所有對象被標記爲白色。

  2. 從 root 開始找到所有可達對象,標記爲灰色,放入待處理隊列。

  3. 遍歷灰色對象隊列,將其引用對象標記爲灰色放入待處理隊列,自身標記爲黑色。

  4. 處理完灰色對象隊列,直到沒有灰色對象。

  5. 剩餘白色對象爲垃圾對象,執行清掃工作。

 

節點複製(Copying Garbage Collection)

節點複製也是基於追蹤的算法。其將整個堆等分爲兩個半區(semi-space),一個包含現有數據,另一個包含已被廢棄的數據。節點複製式垃圾收集從切換(flip)兩個半區的角色開始,然後收集器在老的半區,也就是 Fromspace 中遍歷存活的數據結構,在第一次訪問某個單元時把它複製到新半區,也就是 Tospace 中去。 在 Fromspace 中所有存活單元都被訪問過之後,收集器在 Tospace 中建立一個存活數據結構的副本,用戶程序可以重新開始運行了。

  • 優點
  1. 所有存活的數據結構都縮並地排列在 Tospace 的底部,這樣就不會存在內存碎片的問題
  2. 獲取新內存可以簡單地通過遞增自由空間指針來實現。
  • 缺點
  1. 內存得不到充分利用,總有一半的內存空間處於浪費狀態。

 

分代收集(Generational Garbage Collection)

基於追蹤的垃圾回收算法(標記-清掃、節點複製)一個主要問題是在生命週期較長的對象上浪費時間(長生命週期的對象是不需要頻繁掃描的)。同時,內存分配存在這麼一個事實 “most object die young”。基於這兩點,分代垃圾回收算法將對象按生命週期長短存放到堆上的兩個(或者更多)區域,這些區域就是分代(generation)。對於新生代的區域的垃圾回收頻率要明顯高於老年代區域。

分配對象的時候從新生代裏面分配,如果後面發現對象的生命週期較長,則將其移到老年代,這個過程叫做 promote。隨着不斷 promote,最後新生代的大小在整個堆的佔用比例不會特別大。收集的時候集中主要精力在新生代就會相對來說效率更高,STW 時間也會更短。

  • 優點
  1. 性能更優。
  • 缺點
  1. 實現複雜。

 

 

GC 觸發條件

GC有3種觸發方式:

  1. 輔助GC,在分配內存時,會判斷當前的Heap內存分配量是否達到了觸發一輪GC的閾值(每輪GC完成後,該閾值會被動態設置),如果超過閾值,則啓動一輪GC。
  1. 調用runtime.GC()強制啓動一輪GC。

  2. sysmon是運行時的守護進程,當超過 forcegcperiod (2分鐘)沒有運行GC會啓動一輪GC。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章