搞懂Go垃圾回收

本文主要介紹了垃圾回收的概念,Golang GC的垃圾回收算法和工做原理,看完本文可讓你對Golang垃圾回收機制有個全面的理解。因爲本人不瞭解其餘語言的GC,並未對比其餘語言的垃圾回收算法,須要的能夠自行Google。html

什麼是垃圾回收

垃圾回收(英語:Garbage Collection,縮寫爲GC),在計算機科學中是一種自動的存儲器管理機制。當一個計算機上的動態存儲器再也不須要時,就應該予以釋放,以讓出存儲器,這種存儲器資源管理,稱爲垃圾回收。垃圾回收器可讓程序員減輕許多負擔,也減小程序員犯錯的機會。來自維基百科git

簡單地說,垃圾回收(GC)是在後臺運行一個守護線程,它的做用是在監控各個對象的狀態,識別而且丟棄再也不使用的對象來釋放和重用資源。程序員

go的垃圾回收

當前Golang使用的垃圾回收機制是三色標記發配合寫屏障輔助GC,三色標記法是標記-清除法的一種加強版本。github

標記-清除法(mark and sweep)

原始的標記清楚法分爲兩個步驟:算法

  1. 標記。先STP(Stop The World),暫停整個程序的所有運行線程,將被引用的對象打上標記
  2. 清除沒有被打標機的對象,即回收內存資源,而後恢復運行線程。

這樣作有個很大的問題就是要經過STW保證GC期間標記對象的狀態不能變化,整個程序都要暫停掉,在外部看來程序就會卡頓。markdown

三色標記法

三色標記法是對標記階段的改進,原理以下:併發

  1. 初始狀態全部對象都是白色。
  2. 從root根出發掃描全部根對象(下圖a,b),將他們引用的對象標記爲灰色(圖中A,B)

那麼什麼是root呢? 看了不少文章都沒解釋這這個概念,在這兒說明下:root區域主要是程序運行到當前時刻的棧和全局數據區域。oop

  1. 分析灰色對象是否引用了其餘對象。若是沒有引用其它對象則將該灰色對象標記爲黑色(上圖中A);若是有引用則將它變爲黑色的同時將它引用的對象也變爲灰色(上圖中B引用了D)
  2. 重複步驟3,直到灰色對象隊列爲空。此時白色對象即爲垃圾,進行回收。

也能夠參考下面的動圖輔助理解:性能

Go GC如何工做

上面介紹的是GO GC採用的三色標記算法,可是好像並無體現出來怎麼減小STW對程序的影響呢?實際上是由於Golang GC的大部分處理是和用戶代碼並行的學習

GC期間用戶代碼可能會改變某些對象的狀態,如何實現GC和用戶代碼並行呢?先看下GC工做的完整流程:

  1. Mark: 包含兩部分:
  • Mark Prepare: 初始化GC任務,包括開啓寫屏障(write barrier)和輔助GC(mutator assist),統計root對象的任務數量等。這個過程須要STW
  • GC Drains: 掃描全部root對象,包括全局指針和goroutine(G)棧上的指針(掃描對應G棧時需中止該G),將其加入標記隊列(灰色隊列),並循環處理灰色隊列的對象,直到灰色隊列爲空。該過程後臺並行執行
  1. Mark Termination: 完成標記工做,從新掃描(re-scan)全局指針和棧。由於Mark和用戶程序是並行的,因此在Mark過程當中可能會有新的對象分配和指針賦值,這個時候就須要經過寫屏障(write barrier)記錄下來,re-scan 再檢查一下。這個過程也是會STW的。
  2. Sweep: 按照標記結果回收全部的白色對象,該過程後臺並行執行
  3. Sweep Termination: 對未清掃的span進行清掃, 只有上一輪的GC的清掃工做完成才能夠開始新一輪的GC。 若是標記期間用戶邏輯改變了剛打完標記的對象的引用狀態,怎麼辦呢?

寫屏障(Write Barrier)

寫屏障:該屏障以前的寫操做和以後的寫操做相比,先被系統其它組件感知。 好難懂哦,結合上面GC工做的完整流程就好理解了,就是在每一輪GC開始時會初始化一個叫作「屏障」的東西,而後由它記錄第一次scan時各個對象的狀態,以便和第二次re-scan進行比對,引用狀態變化的對象被標記爲灰色以防止丟失,將屏障先後狀態未變化對象繼續處理。

輔助GC

從上面的GC工做的完整流程能夠看出Golang GC實際上把單次暫停時間分散掉了,原本程序執⾏多是「⽤戶代碼-->⼤段GC-->⽤戶代碼」,那麼分散之後實際上變成了「⽤戶代碼-->⼩段 GC-->⽤戶代碼-->⼩段GC-->⽤戶代碼」這樣。若是GC回收的速度跟不上用戶代碼分配對象的速度呢? Go 語⾔若是發現掃描後回收的速度跟不上分配的速度它依然會把⽤戶邏輯暫停,⽤戶邏輯暫停了之後也就意味着不會有新的對象出現,同時會把⽤戶線程搶過來加⼊到垃圾回收⾥⾯加快垃圾回收的速度。這樣⼀來原來的併發仍是變成了STW,仍是得把⽤戶線程暫停掉,要否則掃描和回收沒完沒了了停不下來,由於新分配對象⽐回收快,因此這種東⻄叫作輔助回收。

如何進行GC調優

衡量GC對程序的影響能夠參考這篇文章,Go 程序的性能調試問題

減小對象的分配,合理重複利用; 避免string與[]byte轉化;

二者發生轉換的時候,底層數據結結構會進行復制,所以致使 gc 效率會變低。

少許使用+鏈接 string;

Go裏面string是最基礎的類型,是一個只讀類型,針對他的每個操做都會建立一個新的string。 若是是少許小文本拼接,用 「+」 就好;若是是大量小文本拼接,用 strings.Join;若是是大量大文本拼接,用 bytes.Buffer。

GC觸發條件

自動垃圾回收的觸發條件有兩個:

  1. 超過內存大小閾值
  2. 達到定時時間 閾值是由一個gcpercent的變量控制的,當新分配的內存佔已在使用中的內存的比例超過gcprecent時就會觸發。好比一次回收完畢後,內存的使用量爲5M,那麼下次回收的時機則是內存分配達到10M的時候。也就是說,並非內存分配越多,垃圾回收頻率越高。 若是一直達不到內存大小的閾值呢?這個時候GC就會被定時時間觸發,好比一直達不到10M,那就定時(默認2min觸發一次)觸發一次GC保證資源的回收。

寫在最後

雖然Golang有自動垃圾回收機制,可是GC不是萬能的,最好仍是養成手動回收內存的習慣:好比手動把再也不使用的內存釋放,把對象置成nil,也能夠考慮在合適的時候調用runtime.GC()觸發GC。

近期在維護的go學習示例代碼,新入坑的朋友們能夠關注下 go-programming

參考:

string討論

Go語言——垃圾回收GC

Golang 垃圾回收剖析

Golang垃圾回收機制詳解

go垃圾回收概要

常見GC算法及Golang GC

相關文章
相關標籤/搜索