golang垃圾回收
GC演算法簡介
檢視:ofollow,noindex" target="_blank">Golang 垃圾回收剖析 中GC演算法簡介
GC效能的評價標準
摘自:https://studygolang.com/artic...
- 吞吐量:是指單位時間內是有多少時間是用來執行user application的。GC佔用的時間過多,就會導致吞吐量較低。
- 最大暫停時間:基本上所有的垃圾回收演算法,都會在執行GC的過程中,暫停user application。如果暫停時間過長,必然會影響使用者體驗,尤其是那些互動性較強的應用。
- 堆使用效率:影響堆使用效率的主要有兩個因素,一個是物件頭部大小,一個是堆的用法。一般來說,堆的頭部越大,儲存的資訊越多,那麼GC的效率就會越高,吞吐量什麼的也會有更佳的表現。但是,我們必須明白,物件頭越小越好。另外,不同的演算法對於堆的不同用法,也會導致堆使用效率差距非常大。比如複製演算法,使用者應用只能使用一般的堆大小。GC是自動管理記憶體的,如果因為GC導致過量佔用堆,那麼就是本末倒置了。
- 訪問的區域性性:具有引用關係的物件之間很可能存在連續訪問的情況。因此,把具有引用關係的物件安排在堆中較近的位置,可以充分利用記憶體訪問區域性性。有的GC演算法會根據引用關係重排物件,比如複製演算法。
設計垃圾回收演算法時,折中無處不在。較大的吞吐量和較短的最大暫停時間往往不可兼得。
寫屏障
golang採用三色法作為GC
的計算方式, 對於已經掃描過的物件, 如果檢測是否由於使用者邏輯的變化而引起的資料變化呢, golang中採用了寫屏障的方式, 對掃描過後的物件使⽤作業系統寫屏障功能⽤來監控⽤戶邏輯這段記憶體。任何時候這段記憶體發⽣引⽤改變的時候就會造成寫屏障發⽣⼀個訊號,垃圾回收器會捕獲到這樣的訊號後就知道這個物件發⽣改變,然後重新掃描這個物件,看看它的引⽤或者被引⽤是否被改變,這樣利⽤狀態的重置從⽽實現當物件狀態發⽣改變的時候依然可以判斷它是活著的還是死的
如何提高GC的效能
觸發GC
GC觸發的時機:2分鐘或者記憶體佔用達到一個閾值(當前堆記憶體佔用是上次gc後對記憶體佔用的兩倍,當GOGC=100時)
GC的總時間
Tgc = Tseq + Tmark + Tsweep
(T表示time)
Tseq
表示是停止使用者的goroutine
和做一些準備活動(通常很小)需要的時間
Tmark
是堆標記時間,標記發生在所有使用者goroutine
停止時,因此可以顯著地影響處理的延遲
Tsweep
是堆清除時間,清除通常與正常的程式運行同時發生,所以對延遲來說是不太關鍵的
1.goroutine
被停止後,GC
將要開始的是時候會做一些準備工作,如寫屏障設定等會執行STW
2.re-scan
的時候執行STW,停止使用者程式,檢驗已經掃描的元素是否發生引用的變化
當前GC的演算法是固定的, 使用者不能夠配置垃圾回收的演算法,唯一能夠更改就是垃圾回收的閥值, 即GOGC
, 用來表示觸發GC的條件。當前能夠提升垃圾回收效率的唯一方式就是減少垃圾
的產生,
可通過下面的方式
-
記憶體分配合理
-
sync.Pool
物件池,重複使用物件, 減少記憶體分配 -
append
使用, 提前設定cap
的數量, 避免無故擴容
輔助回收
主要針對回收能力小於程式處理能力, 需要處理程式計算的資源轉而處理垃圾回收.
實踐中的一些問題
1.當停止大量的請求之後, 記憶體使用量並沒有立即停止下來
原因可能如下:
一是go的垃圾回收有個觸發閾值,這個閾值會隨著每次記憶體使用變大而逐漸增大(如初始閾值是10MB則下一次就是20MB,再下一次就成為了40MB…),如果長時間沒有觸發gc go會主動觸發一次(2min)。高峰時記憶體使用量上去後,除非持續申請記憶體,靠閾值觸發gc已經基本不可能,而是要等最多2min主動gc開始才能觸發gc。
第二個原因是go語言在向系統交還記憶體時只是告訴系統這些記憶體不需要使用了,可以回收;同時作業系統會採取“拖延症”策略,並不是立即回收,而是等到系統記憶體緊張時才會開始回收這樣該程式又重新申請記憶體時就可以獲得極快的分配速度。
2.gc時間長的問題
儘量避免頻繁建立臨時堆物件(如&abc{}, new, make等)以減少垃圾收集時的掃描時間,對於需要頻繁使用的臨時物件考慮直接通過陣列快取進行重用
3.goroutine洩露的問題
我們的一個服務需要處理很多長連線請求,實現時,對於每個長連線請求各開了一個讀取和寫入協程,全部採用endless for loop不停地處理收發資料。當連線被遠端關閉後,如果不對這兩個協程做處理,他們依然會一直執行,並且佔用的channel也不會被釋放…這裡就必須十分注意,在不使用協程後一定要把他依賴的channel close並通過再協程中判斷channel是否關閉以保證其退出。
4.少量使用+連線string
+
來進行string的連線會生成新的物件,降低gc的效率,好的方式是通過append函式來進行。
5.string與[]byte轉化
在stirng與[]byte之間進行轉換,會給gc造成壓力 通過gdb,可以先對比下兩者的資料結構:
type = struct []uint8 {uint8 *array;int len;int cap;}type = struct string {uint8 *str;int len;}
兩者發生轉換的時候,底層資料結結構會進行復制,因此導致gc效率會變低。
解決策略上,一種方式是一直使用[]byte,特別是在資料傳輸方面,[]byte中也包含著許多string會常用到的有效的操作。
另一種是使用更為底層的操作直接進行轉化,避免複製行為的發生。主要是使用unsafe.Pointer直接進行轉化
記憶體洩漏
當一個程序執行時分配了大量的記憶體, 但是由於程式設計上的問題, 導致分配的記憶體不能夠良好的釋放, 從而造成記憶體的浪費, 影響其他的程序使用.
對於C和C++這種沒有Garbage Collection 的語言來講,我們主要關注兩種型別的記憶體洩漏:
堆記憶體洩漏(Heap leak), 對記憶體指的是程式執行中根據需要分配通過malloc
,realloc
,new
等從堆中分配的一塊記憶體,再是完成後必須通過呼叫對應的free
或者delete
刪掉。如果程式的設計的錯誤導致這部分記憶體沒有被釋放,那麼此後這塊記憶體將不會被使用,就會產生Heap Leak.
系統資源洩露(Resource Leak), 主要指程式使用系統分配的資源比如 Bitmap,handle ,SOCKET等沒有使用相應的函式釋放掉,導致系統資源的浪費,嚴重可導致系統效能降低,系統執行不穩定。