Golang垃圾回收 屏障技術

垃圾回收(Garbage Collection,簡稱GC)是編程語言中提供的自動的內存管理機制,自動釋放不須要的對象,讓出存儲器資源,無需程序員手動執行。

Golang中的垃圾回收主要應用三色標記法,GC過程和其餘用戶goroutine可併發運行,但須要必定時間的STW(stop the world),STW的過程當中,CPU不執行用戶代碼,所有用於垃圾回收,這個過程的影響很大,Golang進行了屢次的迭代優化來解決這個問題。


  • 1.3之前的版本使用標記-清掃的方式,整個過程都須要STW。程序員


  • 1.3版本分離了標記和清掃的操做,標記過程STW,清掃過程併發執行。算法


  • 1.5版本在標記過程當中使用三色標記法。回收過程主要有四個階段,其中,標記和清掃都併發執行的,但標記階段的先後須要STW必定時間來作GC的準備工做和棧的re-scan。編程



  1. Sweep Termination: 收集根對象,清掃上一輪未清掃完的span,啓用寫屏障和輔助GC,輔助GC是將必定量的標記和清掃工做交給用戶goroutine來執行,寫屏障在後面會詳細說明微信

  2. Mark: 掃描全部根對象和根對象能夠到達的對象,並標記它們併發

  3. Mark Termination: 完成標記工做,從新掃描部分根對象(要求STW),關閉寫屏障和輔助GCapp

  4. Sweep: 按標記結果清掃對象編程語言


  • 1.8版本引入了混合寫屏障機制(hybrid write barrier),避免了對棧re-scan的過程,極大的減小了STW的時間。編輯器


標記-清掃算法

標記-清掃算法是一種追蹤式的垃圾回收算法,並不會在對象死亡後當即將其清除掉,而是在必定條件下觸發,統一校驗系統中的存活對象,進行回收工做。 在Golang中,有如下幾種狀況可觸發:

  • gcTriggerAlways: 強制觸發GC測試

  • gcTriggerHeap: 當前分配的內存達到必定閾值時觸發,這個閾值在每次GC事後都會根據堆內存的增加狀況和CPU佔用率來調整優化

  • gcTriggerTime: 當必定時間沒有執行過GC就觸發GC(2分鐘)

  • gcTriggerCycle: runtime.GC()調用

標記-清掃分爲兩個部分,標記和清掃,標記過程會遍歷全部對象,查找出死亡對象。 咱們可使用指針的可達性可確認對象的存活,也就是說,若是存在一條從根出發的指針鏈最終可指向某個對象,就認爲這個對象是存活的。 這樣,未能證實存活的對象就能夠標記爲死亡了。
根:是一個有限的指針集合,程序可不通過其餘對象直接訪問這些指針,堆中的對象被加載時,須要先加載根中的指針。在Go中,通常爲goroutine本身的棧空間和全局棧空間。
標記結束後,再次進行遍歷,清除掉確認死亡的對象。



三色標記法
將標記-清掃法使用三色抽象的方式來描述,能夠方便咱們理解回收器的正確性。
回收器經過將對象圖劃分爲三種狀態來指示其掃描過程。 黑色對象爲該對象及其後代已處理且該對象確認存活的,灰色對象爲已經掃描到但未處理完成或者還須要再次處理的,白色對象爲還沒有掃描到或已經死亡的。
如圖所示,回收器從根出發,掃描到可達對象後標記其爲灰色,放入灰色隊列,在掃描灰色對象引用的對象,將他們標記爲灰色,自身標記爲黑色。 掃描所有結束後,剩餘未被掃描到的對象留在白色隊列中,表示已死亡。
標記過程須要STW,由於對象引用關係若是在標記階段作了修改,會影響標記結果的正確性。 例以下圖,灰色對象B中包含指向白色對象C的指針e,對象C還沒有被掃描,此時,若有其餘程序,將e指針從B對象中刪除,並將指向對象C的新指針f插入到黑色對象A中,因爲對象A早已完成掃描,對象C就會一直保持白色狀態直到被回收。
能夠看出,一個白色對象被黑色對象引用,是註定沒法經過這個黑色對象來保證自身存活的,與此同時,若是全部能到達它的灰色對象與它之間的可達關係所有遭到破壞,那麼這個白色對象必然會被視爲垃圾清除掉。 故當上述兩個條件同時知足時,就會出現對象丟失的問題。
若是這個白色對象下游還引用了其餘對象,而且這條路徑是指向下游對象的惟一路徑,那麼他們也是必死無疑的。
爲了防止這種現象的發生,最簡單的方式就是STW,直接禁止掉其餘用戶程序對對象引用關係的干擾,可是STW的過程有明顯的資源浪費,對全部的用戶程序都有很大影響,如何能在保證對象不丟失的狀況下合理的儘量的提升GC效率,減小STW時間呢?
在Golang中使用併發的垃圾回收,也就是多個賦值器與回收器併發執行,與此同時,應用屏障技術來保證回收器的正確性。 其原理主要就是破壞上述兩個條件之一。

屏障技術
當回收器知足下面兩種狀況之一時,便可保證不會出現對象丟失問題。
弱三色不變式: 全部被黑色對象引用的白色對象都處於灰色保護狀態(直接或間接從灰色對象可達)。 強三色不變式: 不存在黑色對象到白色對象的指針。
強三色不變式很好理解,強制性的不容許黑色對象引用白色對象便可。 而弱三色不變式中,黑色對象能夠引用白色對象,可是這個白色對象仍然存在其餘灰色對象對它的引用,或者可達它的鏈路上游存在灰色對象。
三色抽象除了能夠用於描述對象的狀態的,還可用來描述賦值器的狀態,若是一個賦值器已經被回收器掃描完成,則認爲它是黑色的賦值器,若是還沒有掃描過或者還須要從新掃描,則認爲它是灰色的賦值器。
在強三色不變式中,黑色賦值器只存在到黑色對象或灰色對象的指針,由於此時全部黑色對象到白色對象的引用都是被禁止的。 在弱三色不變式中,黑色賦值器容許存在到白色對象的指針,但這個白色對象是被保護的。
上述這些能夠經過屏障技術來保證。
插入屏障
插入屏障攔截將白色指針插入黑色對象的操做,標記其對應對象爲灰色狀態,這樣就不存在黑色對象引用白色對象的狀況了,知足強三色不變式,如上圖例中,在插入指針f時將C對象標記爲灰色。 Go1.5版本使用的Dijkstra寫屏障就是這個原理,僞代碼以下:
writePointer(slot, ptr):
    shade(ptr)
    *slot = ptr
在Golang中,對棧上指針的寫入添加寫屏障的成本很高,因此Go選擇僅對堆上的指針插入增長寫屏障,這樣就會出如今掃描結束後,棧上仍存在引用白色對象的狀況,這時的棧是灰色的,不知足三色不變式,因此須要對棧進行從新掃描使其變黑,完成剩餘對象的標記,這個過程須要STW。 這期間會將全部goroutine掛起,當有大量應用程序時,時間可能會達到10~100ms。
刪除屏障
刪除屏障也是攔截寫操做的,可是是經過保護灰色對象到白色對象的路徑不會斷來實現的。 如上圖例中,在刪除指針e時將對象C標記爲灰色,這樣C下游的全部白色對象,即便會被黑色對象引用,最終也仍是會被掃描標記的,知足了弱三色不變式。 這種方式的回收精度低,一個對象即便被刪除了最後一個指向它的指針也依舊能夠活過這一輪,在下一輪GC中被清理掉。 Yuasa屏障僞代碼以下:
writePointer(slot, ptr):
    if (isGery(slot) || isWhite(slot))
        shade(*slot)
    *slot = ptr
在這種實現方式中,回收器悲觀的認爲全部被刪除的對象均可能會被黑色對象引用。
混合寫屏障
插入屏障和刪除屏障各有優缺點,Dijkstra的插入寫屏障在標記開始時無需STW,可直接開始,併發進行,但結束時須要STW來從新掃描棧,標記棧上引用的白色對象的存活;Yuasa的刪除寫屏障則須要在GC開始時STW掃描堆棧來記錄初始快照,這個過程會保護開始時刻的全部存活對象,但結束時無需STW。Go1.8版本引入的混合寫屏障結合了Yuasa的刪除寫屏障和Dijkstra的插入寫屏障,結合了二者的優勢,僞代碼以下:
writePointer(slot, ptr):
    shade(*slot)
    if current stack is grey:
        shade(ptr)
    *slot = ptr
這裏使用了兩個shade操做,shade(*slot)是刪除寫屏障的變形,例如,一個堆上的灰色對象B,引用白色對象C,在GC併發運行的過程當中,若是棧已掃描置黑,而賦值器將指向C的惟一指針從B中刪除,並讓棧上其餘對象引用它,這時,寫屏障會在刪除指向白色對象C的指針的時候就將C對象置灰,就能夠保護下來了,且它下游的全部對象都處於被保護狀態。 若是對象B在棧上,引用堆上的白色對象C,將其引用關係刪除,且新增一個黑色對象到對象C的引用,那麼就須要經過shade(ptr)來保護了,在指針插入黑色對象時會觸發對對象C的置灰操做。 若是棧已經被掃描過了,那麼棧上引用的對象都是灰色或受灰色保護的白色對象了,因此就沒有必要再進行這步操做。
Golang中的混合寫屏障知足的是變形的弱三色不變式,一樣容許黑色對象引用白色對象,白色對象處於灰色保護狀態,可是隻由堆上的灰色對象保護。 因爲結合了Yuasa的刪除寫屏障和Dijkstra的插入寫屏障的優勢,只須要在開始時併發掃描各個goroutine的棧,使其變黑並一直保持,這個過程不須要STW,而標記結束後,由於棧在掃描後始終是黑色的,也無需再進行re-scan操做了,減小了STW的時間。

本文爲VPGAME科技頭條投稿,做者:鏡萱



「養碼場」
現有技術人80000+
覆蓋JAVA/PHP/IOS/測試等領域
80%級別在P6及以上, 含P9技術大咖30人
技術總監CTO 500餘人


本文分享自微信公衆號 - 養碼場(yangmachang0)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索