Golang垃圾回收机制(一)
原文: http://legendtkl.com/2017/04/28/golang-gc/
1. Golang GC 發(fā)展
Golang 從第一個(gè)版本以來,GC 一直是大家詬病最多的。但是每一個(gè)版本的發(fā)布基本都伴隨著 GC 的改進(jìn)。下面列出一些比較重要的改動(dòng)。
v1.1 STW
v1.3 Mark STW, Sweep 并行
v1.5 三色標(biāo)記法
v1.8 hybrid write barrier
2. GC 算法簡(jiǎn)介
這一小節(jié)介紹三種經(jīng)典的 GC 算法:引用計(jì)數(shù)(reference counting) 、標(biāo)記-清掃(mark & sweep)、節(jié)點(diǎn)復(fù)制(Copying Garbage Collection),分代收集(Generational Garbage Collection)。
2.1 引用計(jì)數(shù)
引用計(jì)數(shù)的思想非常簡(jiǎn)單:每個(gè)單元維護(hù)一個(gè)域,保存其它單元指向它的引用數(shù)量(類似有向圖的入度)。當(dāng)引用數(shù)量為 0 時(shí),將其回收。引用計(jì)數(shù)是漸進(jìn)式的,能夠?qū)?nèi)存管理的開銷分布到整個(gè)程序之中。C++ 的 share_ptr 使用的就是引用計(jì)算方法。
引用計(jì)數(shù)算法實(shí)現(xiàn)一般是把所有的單元放在一個(gè)單元池里,比如類似 free list。這樣所有的單元就被串起來了,就可以進(jìn)行引用計(jì)數(shù)了。新分配的單元計(jì)數(shù)值被設(shè)置為 1(注意不是 0,因?yàn)樯暾?qǐng)一般都說 ptr = new object 這種)。每次有一個(gè)指針被設(shè)為指向該單元時(shí),該單元的計(jì)數(shù)值加 1;而每次刪除某個(gè)指向它的指針時(shí),它的計(jì)數(shù)值減 1。當(dāng)其引用計(jì)數(shù)為 0 的時(shí)候,該單元會(huì)被進(jìn)行回收。雖然這里說的比較簡(jiǎn)單,實(shí)現(xiàn)的時(shí)候還是有很多細(xì)節(jié)需要考慮,比如刪除某個(gè)單元的時(shí)候,那么它指向的所有單元都需要對(duì)引用計(jì)數(shù)減 1。那么如果這個(gè)時(shí)候,發(fā)現(xiàn)其中某個(gè)指向的單元的引用計(jì)數(shù)又為 0,那么是遞歸的進(jìn)行還是采用其他的策略呢?遞歸處理的話會(huì)導(dǎo)致系統(tǒng)顛簸。關(guān)于這些細(xì)節(jié)這里就不討論了,可以參考文章后面的給的參考資料。
優(yōu)點(diǎn):
- 漸進(jìn)式。內(nèi)存管理與用戶程序的執(zhí)行交織在一起,將 GC 的代價(jià)分散到整個(gè)程序。不像標(biāo)記-清掃算法需要 STW (Stop The World,GC 的時(shí)候掛起用戶程序)。
- 算法易于實(shí)現(xiàn)。
- 內(nèi)存單元能夠很快被回收。相比于其他垃圾回收算法,堆被耗盡或者達(dá)到某個(gè)閾值才會(huì)進(jìn)行垃圾回收。
缺點(diǎn):
- 原始的引用計(jì)數(shù)不能處理循環(huán)引用。大概這是被詬病最多的缺點(diǎn)了。不過針對(duì)這個(gè)問題,也除了很多解決方案,比如強(qiáng)引用等。
- 維護(hù)引用計(jì)數(shù)降低運(yùn)行效率。內(nèi)存單元的更新刪除等都需要維護(hù)相關(guān)的內(nèi)存單元的引用計(jì)數(shù),相比于一些追蹤式的垃圾回收算法并不需要這些代價(jià)。
- 單元池 free list 實(shí)現(xiàn)的話不是 cache-friendly 的,這樣會(huì)導(dǎo)致頻繁的 cache miss,降低程序運(yùn)行效率。
2.2 標(biāo)記-清掃
標(biāo)記-清掃算法是第一種自動(dòng)內(nèi)存管理,基于追蹤的垃圾收集算法。算法思想在 70 年代就提出了,是一種非常古老的算法。內(nèi)存單元并不會(huì)在變成垃圾立刻回收,而是保持不可達(dá)狀態(tài),直到到達(dá)某個(gè)閾值或者固定時(shí)間長(zhǎng)度。這個(gè)時(shí)候系統(tǒng)會(huì)掛起用戶程序,也就是 STW,轉(zhuǎn)而執(zhí)行垃圾回收程序。垃圾回收程序?qū)λ械拇婊顔卧M(jìn)行一次全局遍歷確定哪些單元可以回收。算法分兩個(gè)部分:標(biāo)記(mark)和清掃(sweep)。標(biāo)記階段表明所有的存活單元,清掃階段將垃圾單元回收。可視化可以參考下圖。
標(biāo)記-清掃算法的優(yōu)點(diǎn)也就是基于追蹤的垃圾回收算法具有的優(yōu)點(diǎn):避免了引用計(jì)數(shù)算法的缺點(diǎn)(不能處理循環(huán)引用,需要維護(hù)指針)。缺點(diǎn)也很明顯,需要 STW。
三色標(biāo)記算法
三色標(biāo)記算法是對(duì)標(biāo)記階段的改進(jìn),原理如下:
起初所有對(duì)象都是白色。
從根出發(fā)掃描所有可達(dá)對(duì)象,標(biāo)記為灰色,放入待處理隊(duì)列。
從隊(duì)列取出灰色對(duì)象,將其引用對(duì)象標(biāo)記為灰色放入隊(duì)列,自身標(biāo)記為黑色。
重復(fù) 3,直到灰色對(duì)象隊(duì)列為空。此時(shí)白色對(duì)象即為垃圾,進(jìn)行回收。
可視化如下。
三色標(biāo)記的一個(gè)明顯好處是能夠讓用戶程序和 mark 并發(fā)的進(jìn)行,具體可以參考論文:《On-the-fly garbage collection: an exercise in cooperation.》。Golang 的 GC 實(shí)現(xiàn)也是基于這篇論文,后面再具體說明。
2.3 節(jié)點(diǎn)復(fù)制
節(jié)點(diǎn)復(fù)制也是基于追蹤的算法。其將整個(gè)堆等分為兩個(gè)半?yún)^(qū)(semi-space),一個(gè)包含現(xiàn)有數(shù)據(jù),另一個(gè)包含已被廢棄的數(shù)據(jù)。節(jié)點(diǎn)復(fù)制式垃圾收集從切換(flip)兩個(gè)半?yún)^(qū)的角色開始,然后收集器在老的半?yún)^(qū),也就是 Fromspace 中遍歷存活的數(shù)據(jù)結(jié)構(gòu),在第一次訪問某個(gè)單元時(shí)把它復(fù)制到新半?yún)^(qū),也就是 Tospace 中去。在 Fromspace 中所有存活單元都被訪問過之后,收集器在 Tospace 中建立一個(gè)存活數(shù)據(jù)結(jié)構(gòu)的副本,用戶程序可以重新開始運(yùn)行了。
優(yōu)點(diǎn):
所有存活的數(shù)據(jù)結(jié)構(gòu)都縮并地排列在 Tospace 的底部,這樣就不會(huì)存在內(nèi)存碎片的問題。
獲取新內(nèi)存可以簡(jiǎn)單地通過遞增自由空間指針來實(shí)現(xiàn)。
缺點(diǎn):
內(nèi)存得不到充分利用,總有一半的內(nèi)存空間處于浪費(fèi)狀態(tài)。
2.4 分代收集
基于追蹤的垃圾回收算法(標(biāo)記-清掃、節(jié)點(diǎn)復(fù)制)一個(gè)主要問題是在生命周期較長(zhǎng)的對(duì)象上浪費(fèi)時(shí)間(長(zhǎng)生命周期的對(duì)象是不需要頻繁掃描的)。同時(shí),內(nèi)存分配存在這么一個(gè)事實(shí) “most object die young”。基于這兩點(diǎn),分代垃圾回收算法將對(duì)象按生命周期長(zhǎng)短存放到堆上的兩個(gè)(或者更多)區(qū)域,這些區(qū)域就是分代(generation)。對(duì)于新生代的區(qū)域的垃圾回收頻率要明顯高于老年代區(qū)域。
分配對(duì)象的時(shí)候從新生代里面分配,如果后面發(fā)現(xiàn)對(duì)象的生命周期較長(zhǎng),則將其移到老年代,這個(gè)過程叫做 promote。隨著不斷 promote,最后新生代的大小在整個(gè)堆的占用比例不會(huì)特別大。收集的時(shí)候集中主要精力在新生代就會(huì)相對(duì)來說效率更高,STW 時(shí)間也會(huì)更短。
優(yōu)點(diǎn):
性能更優(yōu)。
缺點(diǎn):
實(shí)現(xiàn)復(fù)雜
3. Golang GC
3.1 Overview
在說 Golang 的具體垃圾回收流程時(shí),我們先來看一下幾個(gè)基本的問題。
1. 何時(shí)觸發(fā)GC
在堆上分配大于 32K byte 對(duì)象的時(shí)候進(jìn)行檢測(cè)此時(shí)是否滿足垃圾回收條件,如果滿足則進(jìn)行垃圾回收。
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {...shouldhelpgc := false// 分配的對(duì)象小于 32K byteif size <= maxSmallSize {...} else {shouldhelpgc = true...}...// gcShouldStart() 函數(shù)進(jìn)行觸發(fā)條件檢測(cè)if shouldhelpgc && gcShouldStart(false) {// gcStart() 函數(shù)進(jìn)行垃圾回收gcStart(gcBackgroundMode, false)} }上面是自動(dòng)垃圾回收,還有一種是主動(dòng)垃圾回收,通過調(diào)用 runtime.GC(),這是阻塞式的。
// GC runs a garbage collection and blocks the caller until the // garbage collection is complete. It may also block the entire // program. func GC() {gcStart(gcForceBlockMode, false) }2. GC 觸發(fā)條件
觸發(fā)條件主要關(guān)注下面代碼中的中間部分:forceTrigger || memstats.heap_live >= memstats.gc_trigger 。forceTrigger 是 forceGC 的標(biāo)志;后面半句的意思是當(dāng)前堆上的活躍對(duì)象大于我們初始化時(shí)候設(shè)置的 GC 觸發(fā)閾值。在 malloc 以及 free 的時(shí)候 heap_live 會(huì)一直進(jìn)行更新,這里就不再展開了。
// gcShouldStart returns true if the exit condition for the _GCoff // phase has been met. The exit condition should be tested when // allocating. // // If forceTrigger is true, it ignores the current heap size, but // checks all other conditions. In general this should be false. func gcShouldStart(forceTrigger bool) bool {return gcphase == _GCoff && (forceTrigger || memstats.heap_live >= memstats.gc_trigger) && memstats.enablegc && panicking == 0 && gcpercent >= 0 }//初始化的時(shí)候設(shè)置 GC 的觸發(fā)閾值 func gcinit() {_ = setGCPercent(readgogc())memstats.gc_trigger = heapminimum... } // 啟動(dòng)的時(shí)候通過 GOGC 傳遞百分比 x // 觸發(fā)閾值等于 x * defaultHeapMinimum (defaultHeapMinimum 默認(rèn)是 4M) func readgogc() int32 {p := gogetenv("GOGC")if p == "off" {return -1}if n, ok := atoi32(p); ok {return n}return 100 }3. 垃圾回收的主要流程
三色標(biāo)記法,主要流程如下:
所有對(duì)象最開始都是白色。
從 root 開始找到所有可達(dá)對(duì)象,標(biāo)記為灰色,放入待處理隊(duì)列。
遍歷灰色對(duì)象隊(duì)列,將其引用對(duì)象標(biāo)記為灰色放入待處理隊(duì)列,自身標(biāo)記為黑色。
處理完灰色對(duì)象隊(duì)列,執(zhí)行清掃工作。
詳細(xì)的過程如下圖所示,具體可參考 [9]。
關(guān)于上圖有幾點(diǎn)需要說明的是。
首先從 root 開始遍歷,root 包括全局指針和 goroutine 棧上的指針。
mark 有兩個(gè)過程。
從 root 開始遍歷,標(biāo)記為灰色。遍歷灰色隊(duì)列。
re-scan 全局指針和棧。因?yàn)?mark 和用戶程序是并行的,所以在過程 1 的時(shí)候可能會(huì)有新的對(duì)象分配,這個(gè)時(shí)候就需要通過寫屏障(write barrier)記錄下來。re-scan 再完成檢查一下。
Stop The World 有兩個(gè)過程。
第一個(gè)是 GC 將要開始的時(shí)候,這個(gè)時(shí)候主要是一些準(zhǔn)備工作,比如 enable write barrier。
第二個(gè)過程就是上面提到的 re-scan 過程。如果這個(gè)時(shí)候沒有 stw,那么 mark 將無休止。
另外針對(duì)上圖各個(gè)階段對(duì)應(yīng) GCPhase 如下:
3.2 寫屏障 (write barrier)
關(guān)于 write barrier,完全可以另外寫成一篇文章,所以這里只簡(jiǎn)單介紹一下,這篇文章的重點(diǎn)還是 Golang 的 GC。垃圾回收中的 write barrier 可以理解為編譯器在寫操作時(shí)特意插入的一段代碼,對(duì)應(yīng)的還有 read barrier。
為什么需要 write barrier,很簡(jiǎn)單,對(duì)于和用戶程序并發(fā)運(yùn)行的垃圾回收算法,用戶程序會(huì)一直修改內(nèi)存,所以需要記錄下來。
Golang 1.7 之前的 write barrier 使用的經(jīng)典的 Dijkstra-style insertion write barrier [Dijkstra ‘78], STW 的主要耗時(shí)就在 stack re-scan 的過程。自 1.8 之后采用一種混合的 write barrier 方式 (Yuasa-style deletion write barrier [Yuasa ‘90] 和 Dijkstra-style insertion write barrier [Dijkstra ‘78])來避免 re-scan。具體的可以參考 17503-eliminate-rescan。
3.3 標(biāo)記
下面的源碼還是基于 go1.8rc3。這個(gè)版本的 GC 代碼相比之前改動(dòng)還是挺大的,我們下面盡量只關(guān)注主流程。垃圾回收的代碼主要集中在函數(shù) gcStart() 中。
// gcStart 是 GC 的入口函數(shù),根據(jù) gcMode 做處理。 // 1. gcMode == gcBackgroundMode(后臺(tái)運(yùn)行,也就是并行), _GCoff -> _GCmark // 2. 否則 GCoff -> _GCmarktermination,這個(gè)時(shí)候就是主動(dòng) GC func gcStart(mode gcMode, forceTrigger bool) {... }1. STW phase 1
在 GC 開始之前的準(zhǔn)備工作。
func gcStart(mode gcMode, forceTrigger bool) {...//在后臺(tái)啟動(dòng) mark worker if mode == gcBackgroundMode {gcBgMarkStartWorkers()}...// Stop The Worldsystemstack(stopTheWorldWithSema)...if mode == gcBackgroundMode {// GC 開始前的準(zhǔn)備工作//處理設(shè)置 GCPhase,setGCPhase 還會(huì) enable write barriersetGCPhase(_GCmark)gcBgMarkPrepare() // Must happen before assist enable.gcMarkRootPrepare()// Mark all active tinyalloc blocks. Since we're// allocating from these, they need to be black like// other allocations. The alternative is to blacken// the tiny block on every allocation from it, which// would slow down the tiny allocator.gcMarkTinyAllocs()// Start The Worldsystemstack(startTheWorldWithSema)} else {...} }2. Mark
Mark 階段是并行的運(yùn)行,通過在后臺(tái)一直運(yùn)行 mark worker 來實(shí)現(xiàn)。
func gcStart(mode gcMode, forceTrigger bool) {...//在后臺(tái)啟動(dòng) mark worker if mode == gcBackgroundMode {gcBgMarkStartWorkers()} }func gcBgMarkStartWorkers() {// Background marking is performed by per-P G's. Ensure that// each P has a background GC G.for _, p := range &allp {if p == nil || p.status == _Pdead {break}if p.gcBgMarkWorker == 0 {go gcBgMarkWorker(p)notetsleepg(&work.bgMarkReady, -1)noteclear(&work.bgMarkReady)}} } // gcBgMarkWorker 是一直在后臺(tái)運(yùn)行的,大部分時(shí)候是休眠狀態(tài),通過 gcController 來調(diào)度 func gcBgMarkWorker(_p_ *p) {for {// 將當(dāng)前 goroutine 休眠,直到滿足某些條件gopark(...)...// mark 過程systemstack(func() {// Mark our goroutine preemptible so its stack// can be scanned. This lets two mark workers// scan each other (otherwise, they would// deadlock). We must not modify anything on// the G stack. However, stack shrinking is// disabled for mark workers, so it is safe to// read from the G stack.casgstatus(gp, _Grunning, _Gwaiting)switch _p_.gcMarkWorkerMode {default:throw("gcBgMarkWorker: unexpected gcMarkWorkerMode")case gcMarkWorkerDedicatedMode:gcDrain(&_p_.gcw, gcDrainNoBlock|gcDrainFlushBgCredit)case gcMarkWorkerFractionalMode:gcDrain(&_p_.gcw, gcDrainUntilPreempt|gcDrainFlushBgCredit)case gcMarkWorkerIdleMode:gcDrain(&_p_.gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit)}casgstatus(gp, _Gwaiting, _Grunning)})...} } Mark 階段的標(biāo)記代碼主要在函數(shù) gcDrain() 中實(shí)現(xiàn)。// gcDrain scans roots and objects in work buffers, blackening grey // objects until all roots and work buffers have been drained. func gcDrain(gcw *gcWork, flags gcDrainFlags) {... // Drain root marking jobs.if work.markrootNext < work.markrootJobs {for !(preemptible && gp.preempt) {job := atomic.Xadd(&work.markrootNext, +1) - 1if job >= work.markrootJobs {break}markroot(gcw, job)if idle && pollWork() {goto done}}}// 處理 heap 標(biāo)記// Drain heap marking jobs.for !(preemptible && gp.preempt) {...//從灰色列隊(duì)中取出對(duì)象var b uintptrif blocking {b = gcw.get()} else {b = gcw.tryGetFast()if b == 0 {b = gcw.tryGet()}}if b == 0 {// work barrier reached or tryGet failed.break}//掃描灰色對(duì)象的引用對(duì)象,標(biāo)記為灰色,入灰色隊(duì)列scanobject(b, gcw)} }3. Mark termination (STW phase 2)
mark termination 階段會(huì) stop the world。函數(shù)實(shí)現(xiàn)在 gcMarkTermination()。1.8 版本已經(jīng)不會(huì)再對(duì) goroutine stack 進(jìn)行 re-scan 了。細(xì)節(jié)有點(diǎn)多,這里不細(xì)說了。
func gcMarkTermination() {// World is stopped.// Run gc on the g0 stack. We do this so that the g stack// we're currently running on will no longer change. Cuts// the root set down a bit (g0 stacks are not scanned, and// we don't need to scan gc's internal state). We also// need to switch to g0 so we can shrink the stack.systemstack(func() {gcMark(startTime)// Must return immediately.// The outer function's stack may have moved// during gcMark (it shrinks stacks, including the// outer function's stack), so we must not refer// to any of its variables. Return back to the// non-system stack to pick up the new addresses// before continuing.})... }3.4 清掃
清掃相對(duì)來說就簡(jiǎn)單很多了。
func gcSweep(mode gcMode) {...//阻塞式if !_ConcurrentSweep || mode == gcForceBlockMode {// Special case synchronous sweep....// Sweep all spans eagerly.for sweepone() != ^uintptr(0) {sweep.npausesweep++}// Do an additional mProf_GC, because all 'free' events are now real as well.mProf_GC()mProf_GC()return}// 并行式// Background sweep.lock(&sweep.lock)if sweep.parked {sweep.parked = falseready(sweep.g, 0, true)}unlock(&sweep.lock) } 對(duì)于并行式清掃,在 GC 初始化的時(shí)候就會(huì)啟動(dòng) bgsweep(),然后在后臺(tái)一直循環(huán)。func bgsweep(c chan int) {sweep.g = getg()lock(&sweep.lock)sweep.parked = truec <- 1goparkunlock(&sweep.lock, "GC sweep wait", traceEvGoBlock, 1)for {for gosweepone() != ^uintptr(0) {sweep.nbgsweep++Gosched()}lock(&sweep.lock)if !gosweepdone() {// This can happen if a GC runs between// gosweepone returning ^0 above// and the lock being acquired.unlock(&sweep.lock)continue}sweep.parked = truegoparkunlock(&sweep.lock, "GC sweep wait", traceEvGoBlock, 1)} }func gosweepone() uintptr {var ret uintptrsystemstack(func() {ret = sweepone()})return ret }不管是阻塞式還是并行式,都是通過 sweepone()函數(shù)來做清掃工作的。如果對(duì)于上篇文章 Golang 內(nèi)存管理 熟悉的話,這個(gè)地方就很好理解。內(nèi)存管理都是基于 span 的,mheap_ 是一個(gè)全局的變量,所有分配的對(duì)象都會(huì)記錄在 mheap_ 中。在標(biāo)記的時(shí)候,我們只要找到對(duì)對(duì)象對(duì)應(yīng)的 span 進(jìn)行標(biāo)記,清掃的時(shí)候掃描 span,沒有標(biāo)記的 span 就可以回收了。
// sweeps one span // returns number of pages returned to heap, or ^uintptr(0) if there is nothing to sweep func sweepone() uintptr {...for {s := mheap_.sweepSpans[1-sg/2%2].pop()...if !s.sweep(false) {// Span is still in-use, so this returned no// pages to the heap and the span needs to// move to the swept in-use list.npages = 0}} }// Sweep frees or collects finalizers for blocks not marked in the mark phase. // It clears the mark bits in preparation for the next GC round. // Returns true if the span was returned to heap. // If preserve=true, don't return it to heap nor relink in MCentral lists; // caller takes care of it. func (s *mspan) sweep(preserve bool) bool {... }3.5 其他
1. gcWork
這里介紹一下任務(wù)隊(duì)列,或者說灰色對(duì)象管理。每個(gè) P 上都有一個(gè) gcw 用來管理灰色對(duì)象(get 和 put),gcw 的結(jié)構(gòu)就是 gcWork。gcWork 中的核心是 wbuf1 和 wbuf2,里面存儲(chǔ)就是灰色對(duì)象,或者說是 work(下面就全部統(tǒng)一叫做 work)。
type p struct {...gcw gcWork }type gcWork struct {// wbuf1 and wbuf2 are the primary and secondary work buffers.wbuf1, wbuf2 wbufptr// Bytes marked (blackened) on this gcWork. This is aggregated// into work.bytesMarked by dispose.bytesMarked uint64// Scan work performed on this gcWork. This is aggregated into// gcController by dispose and may also be flushed by callers.scanWork int64 }既然每個(gè) P 上有一個(gè) work buffer,那么是不是還有一個(gè)全局的 work list 呢?是的。通過在每個(gè) P 上綁定一個(gè) work buffer 的好處和 cache 一樣,不需要加鎖。
var work struct {full uint64 // lock-free list of full blocks workbufempty uint64 // lock-free list of empty blocks workbufpad0 [sys.CacheLineSize]uint8 // prevents false-sharing between full/empty and nproc/nwait... }那么為什么使用兩個(gè) work buffer (wbuf1 和 wbuf2)呢?我下面舉個(gè)例子。比如我現(xiàn)在要 get 一個(gè) work 出來,先從 wbuf1 中取,wbuf1 為空的話則與 wbuf2 swap 再 get。在其他時(shí)間將 work buffer 中的 full 或者 empty buffer 移到 global 的 work 中。這樣的好處在于,在 get 的時(shí)候去全局的 work 里面取(多個(gè) goroutine 去取會(huì)有競(jìng)爭(zhēng))。這里有趣的是 global 的 work list 是 lock-free 的,通過原子操作 cas 等實(shí)現(xiàn)。下面列舉幾個(gè)函數(shù)看一下 gcWrok。
初始化
func (w *gcWork) init() {w.wbuf1 = wbufptrOf(getempty())wbuf2 := trygetfull()if wbuf2 == nil {wbuf2 = getempty()}w.wbuf2 = wbufptrOf(wbuf2) }put
// put enqueues a pointer for the garbage collector to trace. // obj must point to the beginning of a heap object or an oblet. func (w *gcWork) put(obj uintptr) {wbuf := w.wbuf1.ptr()if wbuf == nil {w.init()wbuf = w.wbuf1.ptr()// wbuf is empty at this point.} else if wbuf.nobj == len(wbuf.obj) {w.wbuf1, w.wbuf2 = w.wbuf2, w.wbuf1wbuf = w.wbuf1.ptr()if wbuf.nobj == len(wbuf.obj) {putfull(wbuf)wbuf = getempty()w.wbuf1 = wbufptrOf(wbuf)flushed = true}}wbuf.obj[wbuf.nobj] = objwbuf.nobj++ }get
// get dequeues a pointer for the garbage collector to trace, blocking // if necessary to ensure all pointers from all queues and caches have // been retrieved. get returns 0 if there are no pointers remaining. //go:nowritebarrier func (w *gcWork) get() uintptr {wbuf := w.wbuf1.ptr()if wbuf == nil {w.init()wbuf = w.wbuf1.ptr()// wbuf is empty at this point.}if wbuf.nobj == 0 {w.wbuf1, w.wbuf2 = w.wbuf2, w.wbuf1wbuf = w.wbuf1.ptr()if wbuf.nobj == 0 {owbuf := wbufwbuf = getfull()if wbuf == nil {return 0}putempty(owbuf)w.wbuf1 = wbufptrOf(wbuf)}}// TODO: This might be a good place to add prefetch codewbuf.nobj--return wbuf.obj[wbuf.nobj] }2. forcegc
我們上面講了兩種 GC 觸發(fā)方式:自動(dòng)檢測(cè)和用戶主動(dòng)調(diào)用。除此之后 Golang 本身還會(huì)對(duì)運(yùn)行狀態(tài)進(jìn)行監(jiān)控,如果超過兩分鐘沒有 GC,則觸發(fā) GC。監(jiān)控函數(shù)是 sysmon(),在主 goroutine 中啟動(dòng)。
// The main goroutine func main() {...systemstack(func() {newm(sysmon, nil)}) } // Always runs without a P, so write barriers are not allowed. func sysmon() {...for {now := nanotime()unixnow := unixnanotime()lastgc := int64(atomic.Load64(&memstats.last_gc))if gcphase == _GCoff && lastgc != 0 && unixnow-lastgc > forcegcperiod && atomic.Load(&forcegc.idle) != 0 {lock(&forcegc.lock)forcegc.idle = 0forcegc.g.schedlink = 0injectglist(forcegc.g) // 將 forcegc goroutine 加入 runnable queueunlock(&forcegc.lock)}} }var forcegcperiod int64 = 2 * 60 *1e9 //兩分鐘4.參考資料
《Go 語言學(xué)習(xí)筆記》
《垃圾收集》 - 豆瓣
Tracing Garbage Collection - wikipedia
《On-the-fly garbage collection: an exercise in cooperation.》 — Edsger W. Dijkstra, Leslie Lamport, A. J. Martin
Garbage Collection
Tracing Garbage Collection
Copying Garbage Collection – youtube
Generational Garbage Collection – youtube
golang gc talk
17503-eliminate-rescan
轉(zhuǎn)載于:https://www.cnblogs.com/informatics/p/10164321.html
總結(jié)
以上是生活随笔為你收集整理的Golang垃圾回收机制(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php 向二维数组中追加元素
- 下一篇: 前端入门CSS(3)