這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
https://blog.golang.org/go15gc
在開始之前,先來看一個關於Go的記憶體回收在知乎的討論
http://www.zhihu.com/question/21615032
The Setup
Go 正在構建一個面向未來的現代垃圾收集器(garbage collector, GC):一個不僅能夠適應當下軟體開發規模,還能支援下個十年的軟體和硬體能力的長久GC。目前的全停式(stop-the-world)GC已經阻礙了go等安全性語言更加廣泛的運用,在將來,或許已經沒有全停式GC的立足之地。
Go 1.5作為對未來的驚鴻一瞥,達成了我們一年前設定的10毫秒GC延遲的目標。我們在a talk at Gophercon展示了一些令人振奮的資料。其中的延遲改進尤其收到了廣泛關注;Robin Verlangen發表的部落格Billions of requests per day meet Go 1.5讓我們看到了使用者對於新GC的反饋。同時我們也特別滿足於Alan Shreve’s productoin server graphs中其對“天啊,85%的延遲減少!!!”的讚美。
如今16G的記憶體只需要100美元,CPU也擁有越來越多的核,越來越多的超執行緒。十年之後當下的硬體可能會變成古董般的陳設,用Go語言搭建的軟體也需要橫向擴充以適應更多的需求和挑戰。在那樣的硬體基礎上將有提升生產力的能力,Go的記憶體回收設計之初就考慮到了第延遲和易操控的的店。Go 1.5 是向那個方向前進的第一步,這一步將會永遠的影響Go和相關應用的發展。此篇部落格將會系統的介紹我們在Go 1.5的記憶體回收行程中所做的努力。
The Embellishment
為了構建一個面向未來的GC,我們將目光朝向了一個幾十年前出現的演算法。Go的新GC是一個並發(concurrent),三色(tri-color),標記清除(mark-sweep)收集器,源自於Dijkstra in 1978的思路。由此思路引申的最大花費等級垃圾的收集器,我們相信這個演算法能夠適應現代硬體和低延遲軟體系統的需求。
在三色收集器中,每個對象有都分屬於三種顏色(白色,灰色和黑色)之一。我們將對中的對象看待成一張連通圖。每次GC開始之初,所有的對象都是白色的。GC訪問那些根節點對象,包括全域對象,棧中的對象,把他們標記為灰色。然後GC選擇一個灰色對象,把它標記改成黑色,然後沿著指向其他對象的指標去訪問其他對象。當其訪問到一個白色對象,就將這個白色對象標記成灰色。過程重複直到再沒有灰色對象。此時,剩下的白色對象就是不可達的,同時也是應該回收的。(關於三色標記清除的更多知識可以參閱維基百科)。
這一切都在並發的進行,我們可以把每個操作點看作一個突變點,在收集器啟動並執行過程中改變訪問指標。因此,突變點需要在操作過程中維持一個不變數——沒有黑色對象指向白色對象,以免GC喪失對堆中已訪問元素的追蹤。維持這個不變數是由寫屏障來實現的(本身是由突變點在修改指向指標的過程中所執行的一小段程式)。Go的寫屏障將所有現在已達的所有白色節點置為灰色,保證GC最後能夠訪問到所有能訪問的節點。
保持突變點不阻塞的狀態下,判斷遍曆工作是否完成是一件複雜且精妙的工作。為了簡化這個過程,Go 1.5採用儘可能多的並發,然後短暫地全停來檢查所有潛在的灰色節點。找到平衡全停時間和GC工作量之間的甜點是一個可由Go 1.6複用的成果。
當然其中的困難在於實現的細節。比如何時GC開始遍曆?如何確定開始遍曆?GC該如何與Go調度器互動?我們如何去暫停一個運行了足夠長時間突變進程去檢查它的棧?我們怎樣表示白灰黑三種狀態以使我們高效的找到其中的灰色對象?如何找到遍曆的根節點?如何找到對象引用指標?如何最小化記憶體片段?如何最佳化緩衝效能?堆該設定多大?凡此種種,有的在於分配,有的在於尋找,有的在於調度,更多的在於效能。關於這些更加底層的話題的討論已經超出了本文的範疇。
從頂層來看,一個解決效能問題的方案是增加GC的抓手,另一個是提升每個抓手的效能表現。前者編程者可以在他們的應用中調整抓手。缺點就是每過幾年添加一兩個新的抓手,然後每年你都陷入GC 抓手部署的重複勞動當中。Go並不準備那麼做。相反,我們只提供一個GC抓手,稱之為GOGC。這個值控制著你的堆棧的大小,數值相對於你目前可以觸及的對象的大小。預設值100表示當前的堆空間比上次回收時的可以觸及的對象大小要大上100%。200意味著當前堆空間比上次回收對象大小要大上200%(即3倍大小)。如果你想減少花費在GC上的時間,增加GOGC即可。如果你想用更多的GC時間來減少記憶體的時候,則降低GOGC。
更加重要的一點是,隨著下代硬體的發展,記憶體很快就會翻倍,簡單的增加GOGC的大小會減半GC的周期。另一方面,由於GOGC代表的是目前可達對象數目的相對值,可達對象數目變化並不需要你重新調整該參數。應用會自動調整規模。而且,沒有持續增加對各種抓手的支援這樣的重擔,運行時團隊可以把精力集中根據應用方反饋在提升運行時效的主要工作上。
The Punchline
Go 1.5的GC開闢了全停式GC不再是語言安全可靠的障礙。它向人們展示了隨著硬體的發展強大,軟體應用可以不費勁地遷移到新的硬體上,GC並不會成為其阻礙。未來十年以及更遠的將來都將受益於此。更多關於Go 1.5的GC以及我們如何解決延時問題可以參閱GO GC: Latency Problem Solved presentation 或者 講義