
前言
因為之前作為興趣粗略的研究過Java的各種垃圾回收(CMS、G1、ZGC等),今天來大概了解一下Go的GC,如有錯誤,歡迎斧正,一、先來了解一下常見GC演算法
常見的 GC 演算法,參考計數法、復制演算法、標記-清除法、標記整理法、三色標記法、分代收集法,
1. 參考計數法
- 原理是在每個物件內部維護一個整數值,叫做這個物件的參考計數;
- 當物件被參考時參考計數加一,當物件不被參考時參考計數減一,當參考計數為 0
時,自動銷毀物件,
簡單但是速度很慢,缺陷是不能處理回圈參考的情況,
2. 復制演算法、標記-清除法、標記整理法
這哥仨是JVM的,今天不是主角,就簡單說說
- 復制演算法:一塊空間均等割裂成兩塊,存活的復制過去,另一塊清理;其實了解Java的都知道年輕代其實不是均等隔成兩塊,是8:1:1劃分,好處是實作簡單,運行高效,缺點也看到了,記憶體縮小為原來的一半,
- 標記-清除法:首先標記出所有需要回收的物件(Java是通過GCRoot),在標記完成后統一回收所有被標記的物件,缺點:記憶體碎片;
- 標記整理法:標記程序仍然與”標記-清除”演算法一樣,但是后續步驟不是直接對可回收物件進行清理,而是讓所有存活的物件都向一端移動,然后直接清理掉端邊界意外的記憶體,解決記憶體碎片,代價是時間,
3. 分代收集
分代收集是傳統 Mark-Sweep 的一個改進,這個演算法是基于一個經驗:絕大多數物件的生命周期都很短,所以按照物件的生命周期長短來進行分代,
一般 GC 都會分三代,在 java 中稱之為新生代(Young Generation)、年老代(Tenured Generation)和永久代(Permanent Generation);在 .NET 中稱之為第 0 代、第 1 代和第2代,
原理如下:
新物件放入第 0 代
當記憶體用量超過一個較小的閾值時,觸發 0 代收集
第 0 代幸存的物件(未被收集)放入第 1 代
只有當記憶體用量超過一個較高的閾值時,才會觸發 1 代收集
2 代同理
因為 0 代中的物件十分少,所以每次收集時遍歷都會非常快(比 1 代收集快幾個數量級),只有記憶體消耗過于大的時候才會觸發較慢的 1 代和 2 代收集,
因此,分代收集是目前比較好的垃圾回收方式,使用的語言(平臺)有 jvm、.NET ,
4. 三色標記法
三色標記法是傳統 Mark-Sweep 的一個改進,是一個并發的 GC 演算法,
首先做個規定:
- 初始所有物件都是白色(未遍歷到)
- 從根出發能可達的物件是灰色
- 子節點遍歷完的物件是黑色
有了這個,我們來看演算法:
- 初始所有物件都是白色,
- 從根出發掃描所有可達物件,標記為灰色,放入待處理佇列,
- 從佇列取出灰色物件,將其參考物件標記為灰色放入佇列,自身標記為黑色,
- 重復 3,直到灰色物件佇列為空,此時白色物件即為垃圾,進行回收,
像CMS和G1都是用到了這個,只是做了億點點改進,而且三色標記會產生浮動垃圾,這是不可避免的,而且CMS就是在三色標記這塊有個大bug導致STW有時候巨長,所以JDK沒用他做默認垃圾回收器,但是CMS仍然是一個承上啟下的開創性的并發垃圾回收器,
二、現在來看Go GC
1. GC流程
GO的GC是并行GC, 也就是GC的大部分處理和普通的go代碼是同時運行的, 這讓GO的GC流程比較復雜.
- Stack scan:Collect pointers from globals and goroutine stacks,收集根物件(全域變數,和G stack),開啟寫屏障,全域變數、開啟寫屏障需要STW,
- Mark: Mark objects and follow pointers,標記所有根物件,和根物件可以到達的所有物件不被回收,
- Mark Termination: Rescan globals/changed stack, finish mark,重新掃描全域變數,和上一輪改變的stack(寫屏障),完成標記作業,這個程序需要STW,
- Sweep: 按標記結果清掃span
目前整個GC流程會進行兩次STW(Stop The World), 第一次是Stack scan階段, 第二次是Mark Termination階段.
第一次STW會準備根物件的掃描, 啟動寫屏障(Write Barrier)和輔助GC(mutator assist).
第二次STW會重新掃描部分根物件, 禁用寫屏障(Write Barrier)和輔助GC(mutator assist).
從1.8以后的golang將第一步的stop the world 也取消了,這又是一次優化;
1.9開始, 寫屏障的實作使用了Hybrid Write Barrier, 大幅減少了第二次STW的時間.
2. GC觸發條件
主動的話,通過呼叫 runtime.GC(),這是阻塞式的,
自動垃圾回收的觸發條件有兩個:
- 超過記憶體大小閾值
- 達到定時時間
閾值是由一個gcpercent的變數控制的,當新分配的記憶體占已在使用中的記憶體的比例超過gcprecent時就會觸發,
比如一次回收完畢后,記憶體的使用量為5M,那么下次回收的時機則是記憶體分配達到10M的時候,也就是說,并不是記憶體分配越多,垃圾回收頻率越高,
如果一直達不到記憶體大小的閾值呢?這個時候GC就會被定時時間觸發,比如一直達不到10M,那就定時(默認2min觸發一次)觸發一次GC保證資源的回收,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/163951.html
標籤:其他
上一篇:公路修建問題
