深入理解Go-垃圾回收机制

开发 前端
Go的GC自打出生的时候就开始被人诟病,但是在引入v1.5的三色标记和v1.8的混合写屏障后,正常的GC已经缩短到10us左右,已经变得非常优秀,了不起了,我们接下来探索一下Go的GC的原理吧。

Go的GC自打出生的时候就开始被人诟病,但是在引入v1.5的三色标记和v1.8的混合写屏障后,正常的GC已经缩短到10us左右,已经变得非常优秀,了不起了,我们接下来探索一下Go的GC的原理吧

三色标记原理

我们首先看一张图,大概就会对 三色标记法 有一个大致的了解:

原理:

  1.  首先把所有的对象都放到白色的集合中
  2.  从根节点开始遍历对象,遍历到的白色对象从白色集合中放到灰色集合中
  3.  遍历灰色集合中的对象,把灰色对象引用的白色集合的对象放入到灰色集合中,同时把遍历过的灰色集合中的对象放到黑色的集合中
  4.  循环步骤3,知道灰色集合中没有对象
  5.  步骤4结束后,白色集合中的对象就是不可达对象,也就是垃圾,进行回收

写屏障

Go在进行三色标记的时候并没有STW,也就是说,此时的对象还是可以进行修改

那么我们考虑一下,下面的情况

我们在进行三色标记中扫描灰色集合中,扫描到了对象A,并标记了对象A的所有引用,这时候,开始扫描对象D的引用,而此时,另一个goroutine修改了D->E的引用,变成了如下图所示

这样会不会导致E对象就扫描不到了,而被误认为 为白色对象,也就是垃圾

写屏障就是为了解决这样的问题,引入写屏障后,在上述步骤后,E会被认为是存活的,即使后面E被A对象抛弃,E会被在下一轮的GC中进行回收,这一轮GC中是不会对对象E进行回收的

Go1.9中开始启用了混合写屏障,伪代码如下 

  1. writePointer(slot, ptr):  
  2.     shade(*slot)  
  3.     if any stack is grey:  
  4.         shade(ptr)  
  5.     *slot = ptr 

混合写屏障会同时标记指针写入目标的"原指针"和“新指针".

标记原指针的原因是, 其他运行中的线程有可能会同时把这个指针的值复制到寄存器或者栈上的本地变量

因为复制指针到寄存器或者栈上的本地变量不会经过写屏障, 所以有可能会导致指针不被标记, 试想下面的情况: 

  1. [go] b = obj  
  2. [go] oldx = nil  
  3. [gc] scan oldx...  
  4. [go] oldx = b.x // 复制b.x到本地变量, 不进过写屏障  
  5. [go] b.x = ptr // 写屏障应该标记b.x的原值  
  6. [gc] scan b...  
  7. 如果写屏障不标记原值, 那么oldx就不会被扫描到. 

标记新指针的原因是, 其他运行中的线程有可能会转移指针的位置, 试想下面的情况: 

  1. [go] a = ptr  
  2. [go] b = obj  
  3. [gc] scan b...  
  4. [go] b.x = a // 写屏障应该标记b.x的新值  
  5. [go] a = nil  
  6. [gc] scan a...  
  7. 如果写屏障不标记新值, 那么ptr就不会被扫描到. 

混合写屏障可以让GC在并行标记结束后不需要重新扫描各个G的堆栈, 可以减少Mark Termination中的STW时间

除了写屏障外, 在GC的过程中所有新分配的对象都会立刻变为黑色, 在上面的mallocgc函数中可以看到

回收流程

GO的GC是并行GC, 也就是GC的大部分处理和普通的go代码是同时运行的, 这让GO的GC流程比较复杂.

首先GC有四个阶段, 它们分别是:

  •  Sweep Termination: 对未清扫的span进行清扫, 只有上一轮的GC的清扫工作完成才可以开始新一轮的GC
  •  Mark: 扫描所有根对象, 和根对象可以到达的所有对象, 标记它们不被回收
  •  Mark Termination: 完成标记工作, 重新扫描部分根对象(要求STW)
  •  Sweep: 按标记结果清扫span

下图是比较完整的GC流程, 并按颜色对这四个阶段进行了分类:

在GC过程中会有两种后台任务(G), 一种是标记用的后台任务, 一种是清扫用的后台任务.

标记用的后台任务会在需要时启动, 可以同时工作的后台任务数量大约是P的数量的25%, 也就是go所讲的让25%的cpu用在GC上的根据.

清扫用的后台任务在程序启动时会启动一个, 进入清扫阶段时唤醒.

目前整个GC流程会进行两次STW(Stop The World), 第一次是Mark阶段的开始, 第二次是Mark Termination阶段.

第一次STW会准备根对象的扫描, 启动写屏障(Write Barrier)和辅助GC(mutator assist).

第二次STW会重新扫描部分根对象, 禁用写屏障(Write Barrier)和辅助GC(mutator assist).

需要注意的是, 不是所有根对象的扫描都需要STW, 例如扫描栈上的对象只需要停止拥有该栈的G.

写屏障的实现使用了Hybrid Write Barrier, 大幅减少了第二次STW的时间.

源码分析

gcStart 

  1. func gcStart(mode gcMode, trigger gcTrigger) {  
  2.     // Since this is called from malloc and malloc is called in  
  3.     // the guts of a number of libraries that might be holding  
  4.     // locks, don't attempt to start GC in non-preemptible or  
  5.     // potentially unstable situations.  
  6.     // 判断当前g是否可以抢占,不可抢占时不触发GC  
  7.     mp :acquirem()  
  8.     if gp :getg(); gp == mp.g0 || mp.locks > 1 || mp.preemptoff != "" {  
  9.         releasem(mp)  
  10.         return  
  11.     }  
  12.     releasem(mp)  
  13.     mp = nil  
  14.     // Pick up the remaining unswept/not being swept spans concurrently  
  15.     //  
  16.     // This shouldn't happen if we're being invoked in background  
  17.     // mode since proportional sweep should have just finished  
  18.     // sweeping everything, but rounding errors, etc, may leave a  
  19.     // few spans unswept. In forced mode, this is necessary since  
  20.     // GC can be forced at any point in the sweeping cycle.  
  21.     //  
  22.     // We check the transition condition continuously here in case  
  23.     // this G gets delayed in to the next GC cycle.  
  24.     // 清扫 残留的未清扫的垃圾  
  25.     for trigger.test() && gosweepone() != ^uintptr(0) {  
  26.         sweep.nbgsweep++  
  27.     }  
  28.     // Perform GC initialization and the sweep termination  
  29.     // transition.  
  30.     semacquire(&work.startSema)  
  31.     // Re-check transition condition under transition lock.  
  32.     // 判断gcTrriger的条件是否成立  
  33.     if !trigger.test() {  
  34.         semrelease(&work.startSema)  
  35.         return  
  36.     }  
  37.     // For stats, check if this GC was forced by the user  
  38.     // 判断并记录GC是否被强制执行的,runtime.GC()可以被用户调用并强制执行  
  39.     work.userForced = trigger.kind == gcTriggerAlways || trigger.kind == gcTriggerCycle  
  40.     // In gcstoptheworld debug mode, upgrade the mode accordingly.  
  41.     // We do this after re-checking the transition condition so  
  42.     // that multiple goroutines that detect the heap trigger don't  
  43.     // start multiple STW GCs.  
  44.     // 设置gc的mode  
  45.     if mode == gcBackgroundMode {  
  46.         if debug.gcstoptheworld == 1 {  
  47.             mode = gcForceMode  
  48.         } else if debug.gcstoptheworld == 2 {  
  49.             mode = gcForceBlockMode  
  50.         }  
  51.     }  
  52.     // Ok, we're doing it! Stop everybody else  
  53.     semacquire(&worldsema) 
  54.     if trace.enabled {  
  55.         traceGCStart()  
  56.     }  
  57.     // 启动后台标记任务  
  58.     if mode == gcBackgroundMode {  
  59.         gcBgMarkStartWorkers()  
  60.     }  
  61.     // 重置gc 标记相关的状态  
  62.     gcResetMarkState()  
  63.     work.stwprocs, work.maxprocs = gomaxprocs, gomaxprocs  
  64.     if work.stwprocs > ncpu {  
  65.         // This is used to compute CPU time of the STW phases,  
  66.         // so it can't be more than ncpu, even if GOMAXPROCS is.  
  67.         work.stwprocs = ncpu  
  68.     }  
  69.     work.heap0 = atomic.Load64(&memstats.heap_live)  
  70.     work.pauseNS = 0  
  71.     work.mode = mode  
  72.     now :nanotime()  
  73.     work.tSweepTerm = now  
  74.     work.pauseStart = now  
  75.     if trace.enabled {  
  76.         traceGCSTWStart(1)  
  77.     }  
  78.     // STW,停止世界  
  79.     systemstack(stopTheWorldWithSema)  
  80.     // Finish sweep before we start concurrent scan.  
  81.     // 先清扫上一轮的垃圾,确保上轮GC完成  
  82.     systemstack(func() {  
  83.         finishsweep_m()  
  84.     })  
  85.     // clearpools before we start the GC. If we wait they memory will not be  
  86.     // reclaimed until the next GC cycle.  
  87.     // 清理 sync.pool sched.sudogcache、sched.deferpool,这里不展开,sync.pool已经说了,剩余的后面的文章会涉及  
  88.     clearpools()  
  89.     // 增加GC技术  
  90.     work.cycles++  
  91.     if mode == gcBackgroundMode { // Do as much work concurrently as possible  
  92.         gcController.startCycle()  
  93.         work.heapGoal = memstats.next_gc  
  94.         // Enter concurrent mark phase and enable  
  95.         // write barriers. 
  96.         //  
  97.         // Because the world is stopped, all Ps will  
  98.         // observe that write barriers are enabled by  
  99.         // the time we start the world and begin  
  100.         // scanning.  
  101.         //  
  102.         // Write barriers must be enabled before assists are  
  103.         // enabled because they must be enabled before  
  104.         // any non-leaf heap objects are marked. Since  
  105.         // allocations are blocked until assists can  
  106.         // happen, we want enable assists as early as  
  107.         // possible.  
  108.         // 设置GC的状态为 gcMark  
  109.         setGCPhase(_GCmark)  
  110.         // 更新 bgmark 的状态  
  111.         gcBgMarkPrepare() // Must happen before assist enable.  
  112.         // 计算并排队root 扫描任务,并初始化相关扫描任务状态  
  113.         gcMarkRootPrepare()  
  114.         // Mark all active tinyalloc blocks. Since we're  
  115.         // allocating from these, they need to be black like  
  116.         // other allocations. The alternative is to blacken  
  117.         // the tiny block on every allocation from it, which  
  118.         // would slow down the tiny allocator.  
  119.         // 标记 tiny 对象  
  120.         gcMarkTinyAllocs()  
  121.         // At this point all Ps have enabled the write  
  122.         // barrier, thus maintaining the no white to  
  123.         // black invariant. Enable mutator assists to  
  124.         // put back-pressure on fast allocating  
  125.         // mutators.  
  126.         // 设置 gcBlackenEnabled 为 1,启用写屏障  
  127.         atomic.Store(&gcBlackenEnabled, 1)  
  128.         // Assists and workers can start the moment we start  
  129.         // the world.  
  130.         gcController.markStartTime = now  
  131.         // Concurrent mark.  
  132.         systemstack(func() {  
  133.             now = startTheWorldWithSema(trace.enabled)  
  134.         })  
  135.         work.pauseNS += now - work.pauseStart  
  136.         work.tMark = now  
  137.     } else {  
  138.         // 非并行模式  
  139.         // 记录完成标记阶段的开始时间  
  140.         if trace.enabled {  
  141.             // Switch to mark termination STW.  
  142.             traceGCSTWDone()  
  143.             traceGCSTWStart(0)  
  144.         }  
  145.         t :nanotime()  
  146.         work.tMark, work.tMarkTerm = t, t  
  147.         workwork.heapGoal = work.heap0  
  148.         // Perform mark termination. This will restart the world.  
  149.         // stw,进行标记,清扫并start the world 
  150.          gcMarkTermination(memstats.triggerRatio)  
  151.     }  
  152.     semrelease(&work.startSema)  

gcBgMarkStartWorkers

这个函数准备一些 执行bg mark工作的goroutine,但是这些goroutine并不是立即工作的,而是到等到GC的状态被标记为gcMark 才开始工作,见上个函数的119行 

  1. func gcBgMarkStartWorkers() {  
  2.     // Background marking is performed by per-P G's. Ensure that  
  3.     // each P has a background GC G.  
  4.     for _, p :range allp {  
  5.         if p.gcBgMarkWorker == 0 {  
  6.             go gcBgMarkWorker(p)  
  7.             // 等待gcBgMarkWorker goroutine 的 bgMarkReady信号再继续  
  8.             notetsleepg(&work.bgMarkReady, -1)  
  9.             noteclear(&work.bgMarkReady)  
  10.         }  
  11.     }  

gcBgMarkWorker

后台标记任务的函数 

  1. func gcBgMarkWorker(_p_ *p) {  
  2.     gp :getg()  
  3.     // 用于休眠结束后重新获取p和m  
  4.     type parkInfo struct {  
  5.         m      muintptr // Release this m on park.  
  6.         attach puintptr // If non-nil, attach to this p on park.  
  7.     }  
  8.     // We pass park to a gopark unlock function, so it can't be on  
  9.     // the stack (see gopark). Prevent deadlock from recursively  
  10.     // starting GC by disabling preemption.  
  11.     gp.m.preemptoff = "GC worker init"  
  12.     park :new(parkInfo)  
  13.     gp.m.preemptoff = ""  
  14.     // 设置park的m和p的信息,留着后面传给gopark,在被gcController.findRunnable唤醒的时候,便于找回  
  15.     park.m.set(acquirem())  
  16.     park.attach.set(_p_)  
  17.     // Inform gcBgMarkStartWorkers that this worker is ready.  
  18.     // After this point, the background mark worker is scheduled  
  19.     // cooperatively by gcController.findRunnable. Hence, it must  
  20.     // never be preempted, as this would put it into _Grunnable  
  21.     // and put it on a run queue. Instead, when the preempt flag  
  22.     // is set, this puts itself into _Gwaiting to be woken up by  
  23.     // gcController.findRunnable at the appropriate time.  
  24.     // 让gcBgMarkStartWorkers notetsleepg停止等待并继续及退出  
  25.     notewakeup(&work.bgMarkReady)  
  26.     for {  
  27.         // Go to sleep until woken by gcController.findRunnable.  
  28.         // We can't releasem yet since even the call to gopark  
  29.         // may be preempted.  
  30.         // 让g进入休眠  
  31.         gopark(func(g *g, parkp unsafe.Pointer) bool {  
  32.             park := (*parkInfo)(parkp)  
  33.             // The worker G is no longer running, so it's  
  34.             // now safe to allow preemption.  
  35.             // 释放当前抢占的m  
  36.             releasem(park.m.ptr())  
  37.             // If the worker isn't attached to its P,  
  38.             // attach now. During initialization and after  
  39.             // a phase change, the worker may have been  
  40.             // running on a different P. As soon as we  
  41.             // attach, the owner P may schedule the  
  42.             // worker, so this must be done after the G is  
  43.             // stopped.  
  44.             // 设置关联p,上面已经设置过了  
  45.             if park.attach != 0 {  
  46.                 p :park.attach.ptr()  
  47.                 park.attach.set(nil)  
  48.                 // cas the worker because we may be  
  49.                 // racing with a new worker starting  
  50.                 // on this P.  
  51.                 if !p.gcBgMarkWorker.cas(0, guintptr(unsafe.Pointer(g))) {  
  52.                     // The P got a new worker.  
  53.                     // Exit this worker.  
  54.                     return false  
  55.                 }  
  56.             }  
  57.             return true  
  58.         }, unsafe.Pointer(park), waitReasonGCWorkerIdle, traceEvGoBlock, 0)  
  59.         // Loop until the P dies and disassociates this  
  60.         // worker (the P may later be reused, in which case  
  61.         // it will get a new worker) or we failed to associate.  
  62.         // 检查P的gcBgMarkWorker是否和当前的G一致, 不一致时结束当前的任务  
  63.         if _p_.gcBgMarkWorker.ptr() != gp {  
  64.             break  
  65.         }  
  66.         // Disable preemption so we can use the gcw. If the  
  67.         // scheduler wants to preempt us, we'll stop draining,  
  68.         // dispose the gcw, and then preempt.  
  69.         // gopark第一个函数中释放了m,这里再抢占回来  
  70.         park.m.set(acquirem())  
  71.         if gcBlackenEnabled == 0 {  
  72.             throw("gcBgMarkWorker: blackening not enabled")  
  73.         }  
  74.         startTime :nanotime()  
  75.         // 设置gcmark的开始时间  
  76.         _p_.gcMarkWorkerStartTime = startTime  
  77.         decnwait :atomic.Xadd(&work.nwait, -1)  
  78.         if decnwait == work.nproc {  
  79.             println("runtime: workwork.nwait=", decnwait, "work.nproc=", work.nproc)  
  80.             throw("work.nwait was > work.nproc")  
  81.         }  
  82.         // 切换到g0工作  
  83.         systemstack(func() {  
  84.             // Mark our goroutine preemptible so its stack  
  85.             // can be scanned. This lets two mark workers  
  86.             // scan each other (otherwise, they would  
  87.             // deadlock). We must not modify anything on  
  88.             // the G stack. However, stack shrinking is  
  89.             // disabled for mark workers, so it is safe to  
  90.             // read from the G stack.  
  91.             // 设置G的状态为waiting,以便于另一个g扫描它的栈(两个g可以互相扫描对方的栈)  
  92.             casgstatus(gp, _Grunning, _Gwaiting)  
  93.             switch _p_.gcMarkWorkerMode {  
  94.             default:  
  95.                 throw("gcBgMarkWorker: unexpected gcMarkWorkerMode")  
  96.             case gcMarkWorkerDedicatedMode:  
  97.                 // 专心执行标记工作的模式  
  98.                 gcDrain(&_p_.gcw, gcDrainUntilPreempt|gcDrainFlushBgCredit)  
  99.                 if gp.preempt {  
  100.                     // 被抢占了,把所有本地运行队列中的G放到全局运行队列中  
  101.                     // We were preempted. This is  
  102.                     // a useful signal to kick  
  103.                     // everything out of the run  
  104.                     // queue so it can run  
  105.                     // somewhere else.  
  106.                     lock(&sched.lock)  
  107.                     for {  
  108.                         gp, _ :runqget(_p_)  
  109.                         if gp == nil {  
  110.                             break  
  111.                         }  
  112.                         globrunqput(gp)  
  113.                     }  
  114.                     unlock(&sched.lock)  
  115.                 }  
  116.                 // Go back to draining, this time  
  117.                 // without preemption.  
  118.                 // 继续执行标记工作  
  119.                 gcDrain(&_p_.gcw, gcDrainNoBlock|gcDrainFlushBgCredit)  
  120.             case gcMarkWorkerFractionalMode:  
  121.                 // 执行标记工作,知道被抢占  
  122.                 gcDrain(&_p_.gcw, gcDrainFractional|gcDrainUntilPreempt|gcDrainFlushBgCredit)  
  123.             case gcMarkWorkerIdleMode:  
  124.                 // 空闲的时候执行标记工作  
  125.                 gcDrain(&_p_.gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit)  
  126.             }  
  127.             // 把G的waiting状态转换到runing状态  
  128.             casgstatus(gp, _Gwaiting, _Grunning)  
  129.         })  
  130.         // If we are nearing the end of mark, dispose  
  131.         // of the cache promptly. We must do this  
  132.         // before signaling that we're no longer  
  133.         // working so that other workers can't observe  
  134.         // no workers and no work while we have this  
  135.         // cached, and before we compute done.  
  136.         // 及时处理本地缓存,上交到全局的队列中  
  137.         if gcBlackenPromptly {  
  138.             _p_.gcw.dispose()  
  139.         }  
  140.         // Account for time.  
  141.         // 累加耗时  
  142.         duration :nanotime() - startTime  
  143.         switch _p_.gcMarkWorkerMode {  
  144.         case gcMarkWorkerDedicatedMode:  
  145.             atomic.Xaddint64(&gcController.dedicatedMarkTime, duration)  
  146.             atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, 1)  
  147.         case gcMarkWorkerFractionalMode:  
  148.             atomic.Xaddint64(&gcController.fractionalMarkTime, duration)  
  149.             atomic.Xaddint64(&_p_.gcFractionalMarkTime, duration)  
  150.         case gcMarkWorkerIdleMode:  
  151.             atomic.Xaddint64(&gcController.idleMarkTime, duration)  
  152.         }  
  153.         // Was this the last worker and did we run out  
  154.         // of work?  
  155.         incnwait :atomic.Xadd(&work.nwait, +1)  
  156.         if incnwait > work.nproc {  
  157.             println("runtime: p.gcMarkWorkerMode=", _p_.gcMarkWorkerMode,  
  158.                 "workwork.nwait=", incnwait, "work.nproc=", work.nproc)  
  159.             throw("work.nwait > work.nproc")  
  160.         }  
  161.         // If this worker reached a background mark completion  
  162.         // point, signal the main GC goroutine.  
  163.         if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {  
  164.             // Make this G preemptible and disassociate it  
  165.             // as the worker for this P so  
  166.             // findRunnableGCWorker doesn't try to  
  167.             // schedule it.  
  168.             // 取消p m的关联  
  169.             _p_.gcBgMarkWorker.set(nil)  
  170.             releasem(park.m.ptr())  
  171.             gcMarkDone()  
  172.             // Disable preemption and prepare to reattach  
  173.             // to the P.  
  174.             //  
  175.             // We may be running on a different P at this  
  176.             // point, so we can't reattach until this G is  
  177.             // parked.  
  178.             park.m.set(acquirem())  
  179.             park.attach.set(_p_)  
  180.         }  
  181.     }  

gcDrain

三色标记的主要实现

gcDrain扫描所有的roots和对象,并表黑灰色对象,知道所有的roots和对象都被标记 

  1. func gcDrain(gcw *gcWork, flags gcDrainFlags) {  
  2.     if !writeBarrier.needed {  
  3.         throw("gcDrain phase incorrect")  
  4.     }  
  5.     gp :getg().m.curg  
  6.     // 看到抢占标识是否要返回  
  7.     preemptible :flags&gcDrainUntilPreempt != 0  
  8.     // 没有任务时是否要等待任务  
  9.     blocking :flags&(gcDrainUntilPreempt|gcDrainIdle|gcDrainFractional|gcDrainNoBlock) == 0  
  10.     // 是否计算后台的扫描量来减少辅助GC和唤醒等待中的G  
  11.     flushBgCredit :flags&gcDrainFlushBgCredit != 0  
  12.     // 是否在空闲的时候执行标记任务  
  13.     idle :flags&gcDrainIdle != 0  
  14.     // 记录初始的已经执行过的扫描任务  
  15.     initScanWork :gcw.scanWork  
  16.     // checkWork is the scan work before performing the next  
  17.     // self-preempt check.  
  18.     // 设置对应模式的工作检查函数  
  19.     checkWork :int64(1<<63 - 1)  
  20.     var check func() bool  
  21.     if flags&(gcDrainIdle|gcDrainFractional) != 0 {  
  22.         checkWork = initScanWork + drainCheckThreshold  
  23.         if idle {  
  24.             check = pollWork  
  25.         } else if flags&gcDrainFractional != 0 {  
  26.             check = pollFractionalWorkerExit  
  27.         }  
  28.     }  
  29.     // Drain root marking jobs.  
  30.     // 如果root对象没有扫描完,则扫描  
  31.     if work.markrootNext < work.markrootJobs {  
  32.         for !(preemptible && gp.preempt) {  
  33.             job :atomic.Xadd(&work.markrootNext, +1) - 1  
  34.             if job >= work.markrootJobs {  
  35.                 break  
  36.             }  
  37.             // 执行root扫描任务  
  38.             markroot(gcw, job)  
  39.             if check != nil && check() {  
  40.                 goto done  
  41.             }  
  42.         }  
  43.     }  
  44.     // Drain heap marking jobs.  
  45.     // 循环直到被抢占  
  46.     for !(preemptible && gp.preempt) {  
  47.         // Try to keep work available on the global queue. We used to  
  48.         // check if there were waiting workers, but it's better to  
  49.         // just keep work available than to make workers wait. In the  
  50.         // worst case, we'll do O(log(_WorkbufSize)) unnecessary  
  51.         // balances.  
  52.         if work.full == 0 {  
  53.             // 平衡工作,如果全局的标记队列为空,则分一部分工作到全局队列中  
  54.             gcw.balance()  
  55.         }  
  56.         var b uintptr  
  57.         if blocking {  
  58.             b = gcw.get()  
  59.         } else {  
  60.             b = gcw.tryGetFast()  
  61.             if b == 0 {  
  62.                 b = gcw.tryGet()  
  63.             }  
  64.         }  
  65.         // 获取任务失败,跳出循环  
  66.         if b == 0 {  
  67.             // work barrier reached or tryGet failed.  
  68.             break  
  69.         }  
  70.         // 扫描获取的到对象  
  71.         scanobject(b, gcw)  
  72.         // Flush background scan work credit to the global  
  73.         // account if we've accumulated enough locally so  
  74.         // mutator assists can draw on it.  
  75.         // 如果当前扫描的数量超过了 gcCreditSlack,就把扫描的对象数量加到全局的数量,批量更新  
  76.         if gcw.scanWork >= gcCreditSlack {  
  77.             atomic.Xaddint64(&gcController.scanWork, gcw.scanWork)  
  78.             if flushBgCredit {  
  79.                 gcFlushBgCredit(gcw.scanWork - initScanWork)  
  80.                 initScanWork = 0  
  81.             }  
  82.             checkWork -gcw.scanWork  
  83.             gcw.scanWork = 0  
  84.             // 如果扫描的对象数量已经达到了 执行下次抢占的目标数量 checkWork, 则调用对应模式的函数  
  85.             // idle模式为 pollWork, Fractional模式为 pollFractionalWorkerExit ,在第20行  
  86.             if checkWork <= 0 {  
  87.                 checkWork += drainCheckThreshold  
  88.                 if check != nil && check() {  
  89.                     break  
  90.                 }  
  91.             }  
  92.         }  
  93.     }  
  94.     // In blocking mode, write barriers are not allowed after this  
  95.     // point because we must preserve the condition that the work  
  96.     // buffers are empty.  
  97. done:  
  98.     // Flush remaining scan work credit.  
  99.     if gcw.scanWork > 0 {  
  100.         // 把扫描的对象数量添加到全局  
  101.         atomic.Xaddint64(&gcController.scanWork, gcw.scanWork)  
  102.         if flushBgCredit {  
  103.             gcFlushBgCredit(gcw.scanWork - initScanWork)  
  104.         }  
  105.         gcw.scanWork = 0  
  106.     }  

markroot

这个被用于根对象扫描 

  1. func markroot(gcw *gcWork, i uint32) {  
  2.     // TODO(austin): This is a bit ridiculous. Compute and store  
  3.     // the bases in gcMarkRootPrepare instead of the counts.  
  4.     baseFlushCache :uint32(fixedRootCount)  
  5.     baseData :baseFlushCache + uint32(work.nFlushCacheRoots)  
  6.     baseBSS :baseData + uint32(work.nDataRoots)  
  7.     baseSpans :baseBSS + uint32(work.nBSSRoots)  
  8.     baseStacks :baseSpans + uint32(work.nSpanRoots)  
  9.     end :baseStacks + uint32(work.nStackRoots)  
  10.     // Note: if you add a case here, please also update heapdump.go:dumproots.  
  11.     switch {  
  12.     // 释放mcache中的span  
  13.     case baseFlushCache <= i && i < baseData:  
  14.         flushmcache(int(i - baseFlushCache))  
  15.     // 扫描可读写的全局变量  
  16.     case baseData <= i && i < baseBSS:  
  17.         for _, datap :range activeModules() {  
  18.             markrootBlock(datap.data, datap.edata-datap.data, datap.gcdatamask.bytedata, gcw, int(i-baseData))  
  19.         }  
  20.     // 扫描只读的全局队列  
  21.     case baseBSS <= i && i < baseSpans:  
  22.         for _, datap :range activeModules() {  
  23.             markrootBlock(datap.bss, datap.ebss-datap.bss, datap.gcbssmask.bytedata, gcw, int(i-baseBSS))  
  24.         }  
  25.     // 扫描Finalizer队列  
  26.     case i == fixedRootFinalizers:  
  27.         // Only do this once per GC cycle since we don't call  
  28.         // queuefinalizer during marking.  
  29.         if work.markrootDone {  
  30.             break  
  31.         }  
  32.         for fb :allfin; fb != nil; fbfb = fb.alllink {  
  33.             cnt :uintptr(atomic.Load(&fb.cnt))  
  34.             scanblock(uintptr(unsafe.Pointer(&fb.fin[0])), cnt*unsafe.Sizeof(fb.fin[0]), &finptrmask[0], gcw)  
  35.         }  
  36.     // 释放已经终止的stack  
  37.     case i == fixedRootFreeGStacks:  
  38.         // Only do this once per GC cycle; preferably  
  39.         // concurrently.  
  40.         if !work.markrootDone {  
  41.             // Switch to the system stack so we can call  
  42.             // stackfree.  
  43.             systemstack(markrootFreeGStacks)  
  44.         }  
  45.     // 扫描MSpan.specials  
  46.     case baseSpans <= i && i < baseStacks:  
  47.         // mark MSpan.specials  
  48.         markrootSpans(gcw, int(i-baseSpans))  
  49.     default:  
  50.         // the rest is scanning goroutine stacks  
  51.         // 获取需要扫描的g  
  52.         var gp *g  
  53.         if baseStacks <= i && i < end {  
  54.             gp = allgs[i-baseStacks]  
  55.         } else {  
  56.             throw("markroot: bad index")  
  57.         }  
  58.         // remember when we've first observed the G blocked  
  59.         // needed only to output in traceback  
  60.         status :readgstatus(gp) // We are not in a scan state  
  61.         if (status == _Gwaiting || status == _Gsyscall) && gp.waitsince == 0 {  
  62.             gp.waitsince = work.tstart  
  63.         }  
  64.         // scang must be done on the system stack in case  
  65.         // we're trying to scan our own stack.  
  66.         // 转交给g0进行扫描  
  67.         systemstack(func() {  
  68.             // If this is a self-scan, put the user G in  
  69.             // _Gwaiting to prevent self-deadlock. It may  
  70.             // already be in _Gwaiting if this is a mark  
  71.             // worker or we're in mark termination.  
  72.             userG :getg().m.curg  
  73.             selfScan :gp == userG && readgstatus(userG) == _Grunning  
  74.             // 如果是扫描自己的,则转换自己的g的状态  
  75.             if selfScan {  
  76.                 casgstatus(userG, _Grunning, _Gwaiting)  
  77.                 userG.waitreason = waitReasonGarbageCollectionScan  
  78.             }  
  79.             // TODO: scang blocks until gp's stack has  
  80.             // been scanned, which may take a while for  
  81.             // running goroutines. Consider doing this in  
  82.             // two phases where the first is non-blocking:  
  83.             // we scan the stacks we can and ask running  
  84.             // goroutines to scan themselves; and the  
  85.             // second blocks.  
  86.             // 扫描g的栈  
  87.             scang(gp, gcw)  
  88.             if selfScan {  
  89.                 casgstatus(userG, _Gwaiting, _Grunning)  
  90.             }  
  91.         })  
  92.     }  

markRootBlock

根据 ptrmask0,来扫描[b0, b0+n0)区域

  1. func markrootBlock(b0, n0 uintptr, ptrmask0 *uint8, gcw *gcWork, shard int) {  
  2.     if rootBlockBytes%(8*sys.PtrSize) != 0 {  
  3.         // This is necessary to pick byte offsets in ptrmask0.  
  4.         throw("rootBlockBytes must be a multiple of 8*ptrSize")  
  5.     }  
  6.     b :b0 + uintptr(shard)*rootBlockBytes  
  7.     // 如果需扫描的block区域,超出b0+n0的区域,直接返回  
  8.     if b >= b0+n0 {  
  9.         return  
  10.     }  
  11.     ptrmask := (*uint8)(add(unsafe.Pointer(ptrmask0), uintptr(shard)*(rootBlockBytes/(8*sys.PtrSize))))  
  12.     n :uintptr(rootBlockBytes)  
  13.     if b+n > b0+n0 {  
  14.         n = b0 + n0 - b  
  15.     }  
  16.     // Scan this shard.  
  17.     // 扫描给定block的shard  
  18.     scanblock(b, n, ptrmask, gcw)  

scanblock 

  1. func scanblock(b0, n0 uintptr, ptrmask *uint8, gcw *gcWork) {  
  2.     // Use local copies of original parameters, so that a stack trace  
  3.     // due to one of the throws below shows the original block  
  4.     // base and extent.  
  5.     b :b0  
  6.     n :n0  
  7.     for i :uintptr(0); i < n; {  
  8.         // Find bits for the next word.  
  9.         // 找到bitmap中对应的bits  
  10.         bits :uint32(*addb(ptrmask, i/(sys.PtrSize*8)))  
  11.         if bits == 0 {  
  12.             i += sys.PtrSize * 8  
  13.             continue  
  14.         }  
  15.         for j :0; j < 8 && i < n; j++ {  
  16.             if bits&1 != 0 {  
  17.                 // 如果该地址包含指针  
  18.                 // Same work as in scanobject; see comments there.  
  19.                 obj := *(*uintptr)(unsafe.Pointer(b + i))  
  20.                 if obj != 0 {  
  21.                     // 如果该地址下找到了对应的对象,标灰  
  22.                     if obj, span, objIndex :findObject(obj, b, i); obj != 0 {  
  23.                         greyobject(obj, b, i, span, gcw, objIndex)  
  24.                     }  
  25.                 }  
  26.             }  
  27.             bits >>= 1  
  28.             i += sys.PtrSize  
  29.         }  
  30.     }  

greyobject

标灰对象其实就是找到对应bitmap,标记存活并扔进队列 

  1. func greyobject(obj, base, off uintptr, span *mspan, gcw *gcWork, objIndex uintptr) {  
  2.     // obj should be start of allocation, and so must be at least pointer-aligned.  
  3.     if obj&(sys.PtrSize-1) != 0 {  
  4.         throw("greyobject: obj not pointer-aligned")  
  5.     }  
  6.     mbits :span.markBitsForIndex(objIndex)  
  7.     if useCheckmark {  
  8.         // 这里是用来debug,确保所有的对象都被正确标识  
  9.         if !mbits.isMarked() {  
  10.             // 这个对象没有被标记  
  11.             printlock()  
  12.             print("runtime:greyobject: checkmarks finds unexpected unmarked object obj=", hex(obj), "\n")  
  13.             print("runtime: found obj at *(", hex(base), "+", hex(off), ")\n")  
  14.             // Dump the source (base) object  
  15.             gcDumpObject("base", base, off)  
  16.             // Dump the object  
  17.             gcDumpObject("obj", obj, ^uintptr(0))  
  18.             getg().m.traceback = 2  
  19.             throw("checkmark found unmarked object")  
  20.         }  
  21.         hbits :heapBitsForAddr(obj)  
  22.         if hbits.isCheckmarked(span.elemsize) {  
  23.             return  
  24.         }  
  25.         hbits.setCheckmarked(span.elemsize)  
  26.         if !hbits.isCheckmarked(span.elemsize) {  
  27.             throw("setCheckmarked and isCheckmarked disagree")  
  28.         }  
  29.     } else {  
  30.         if debug.gccheckmark > 0 && span.isFree(objIndex) {  
  31.             print("runtime: marking free object ", hex(obj), " found at *(", hex(base), "+", hex(off), ")\n")  
  32.             gcDumpObject("base", base, off)  
  33.             gcDumpObject("obj", obj, ^uintptr(0))  
  34.             getg().m.traceback = 2  
  35.             throw("marking free object")  
  36.         }  
  37.         // If marked we have nothing to do.  
  38.         // 对象被正确标记了,无需做其他的操作  
  39.         if mbits.isMarked() {  
  40.             return  
  41.         }  
  42.         // mbits.setMarked() // Avoid extra call overhead with manual inlining.  
  43.         // 标记对象  
  44.         atomic.Or8(mbits.bytep, mbits.mask)  
  45.         // If this is a noscan object, fast-track it to black  
  46.         // instead of greying it.  
  47.         // 如果对象不是指针,则只需要标记,不需要放进队列,相当于直接标黑  
  48.         if span.spanclass.noscan() {  
  49.             gcw.bytesMarked += uint64(span.elemsize)  
  50.             return  
  51.         }  
  52.     }  
  53.     // Queue the obj for scanning. The PREFETCH(obj) logic has been removed but  
  54.     // seems like a nice optimization that can be added back in.  
  55.     // There needs to be time between the PREFETCH and the use.  
  56.     // Previously we put the obj in an 8 element buffer that is drained at a rate  
  57.     // to give the PREFETCH time to do its work.  
  58.     // Use of PREFETCHNTA might be more appropriate than PREFETCH  
  59.     // 判断对象是否被放进队列,没有则放入,标灰步骤完成  
  60.     if !gcw.putFast(obj) {  
  61.         gcw.put(obj)  
  62.     }  

gcWork.putFast

work有wbuf1 wbuf2两个队列用于保存灰色对象,首先会往wbuf1队列里加入灰色对象,wbuf1满了后,交换wbuf1和wbuf2,这事wbuf2便晋升为wbuf1,继续存放灰色对象,两个队列都满了,则想全局进行申请

putFast这里进尝试将对象放进wbuf1队列中 

  1. func (w *gcWork) putFast(obj uintptr) bool {  
  2.     wbuf :w.wbuf1  
  3.     if wbuf == nil {  
  4.         // 没有申请缓存队列,返回false  
  5.         return false  
  6.     } else if wbuf.nobj == len(wbuf.obj) {  
  7.         // wbuf1队列满了,返回false  
  8.         return false  
  9.     }  
  10.     // 向未满wbuf1队列中加入对象  
  11.     wbuf.obj[wbuf.nobj] = obj  
  12.     wbuf.nobj++  
  13.     return true  

gcWork.put

put不仅尝试将对象放入wbuf1,还会再wbuf1满的时候,尝试更换wbuf1 wbuf2的角色,都满的话,则想全局进行申请,并将满的队列上交到全局队列 

  1. func (w *gcWork) put(obj uintptr) {  
  2.     flushed :false  
  3.     wbuf :w.wbuf1  
  4.     if wbuf == nil {  
  5.         // 如果wbuf1不存在,则初始化wbuf1 wbuf2两个队列  
  6.         w.init()  
  7.         wwbuf = w.wbuf1  
  8.         // wbuf is empty at this point.  
  9.     } else if wbuf.nobj == len(wbuf.obj) {  
  10.         // wbuf1满了,更换wbuf1 wbuf2的角色  
  11.         w.wbuf1, ww.wbuf2 = w.wbuf2, w.wbuf1  
  12.         wwbuf = w.wbuf1  
  13.         if wbuf.nobj == len(wbuf.obj) {  
  14.             // 更换角色后,wbuf1也满了,说明两个队列都满了  
  15.             // 把 wbuf1上交全局并获取一个空的队列  
  16.             putfull(wbuf)  
  17.             wbuf = getempty()  
  18.             w.wbuf1 = wbuf  
  19.             // 设置队列上交的标志位  
  20.             flushed = true  
  21.         }  
  22.     }  
  23.     wbuf.obj[wbuf.nobj] = obj  
  24.     wbuf.nobj++  
  25.     // If we put a buffer on full, let the GC controller know so  
  26.     // it can encourage more workers to run. We delay this until  
  27.     // the end of put so that w is in a consistent state, since  
  28.     // enlistWorker may itself manipulate w.  
  29.     // 此时全局已经有标记满的队列,GC controller选择调度更多work进行工作  
  30.     if flushed && gcphase == _GCmark {  
  31.         gcController.enlistWorker()  
  32.     }  

到这里,接下来,我们继续分析gcDrain里面的函数,追踪一下,我们标灰的对象是如何被标黑的

gcw.balance()

继续分析 gcDrain的58行,balance work是什么 

  1. func (w *gcWork) balance() {  
  2.     if w.wbuf1 == nil {  
  3.         // 这里wbuf1 wbuf2队列还没有初始化  
  4.         return  
  5.     }  
  6.     // 如果wbuf2不为空,则上交到全局,并获取一个空岛队列给wbuf2  
  7.     if wbuf :w.wbuf2; wbuf.nobj != 0 {  
  8.         putfull(wbuf)  
  9.         w.wbuf2 = getempty()  
  10.     } else if wbuf :w.wbuf1; wbuf.nobj > 4 {  
  11.         // 把未满的wbuf1分成两半,并把其中一半上交的全局队列  
  12.         w.wbuf1 = handoff(wbuf)  
  13.     } else {  
  14.         return  
  15.     }  
  16.     // We flushed a buffer to the full list, so wake a worker.  
  17.     // 这里,全局队列有满的队列了,其他work可以工作了  
  18.     if gcphase == _GCmark {  
  19.         gcController.enlistWorker()  
  20.     }  

gcw.get()

继续分析 gcDrain的63行,这里就是首先从本地的队列获取一个对象,如果本地队列的wbuf1没有,尝试从wbuf2获取,如果两个都没有,则尝试从全局队列获取一个满的队列,并获取一个对象 

  1. func (w *gcWork) get() uintptr {  
  2.     wbuf :w.wbuf1  
  3.     if wbuf == nil {  
  4.         w.init()  
  5.         wwbuf = w.wbuf1  
  6.         // wbuf is empty at this point.  
  7.     }  
  8.     if wbuf.nobj == 0 {  
  9.         // wbuf1空了,更换wbuf1 wbuf2的角色  
  10.         w.wbuf1, ww.wbuf2 = w.wbuf2, w.wbuf1  
  11.         wwbuf = w.wbuf1  
  12.         // 原wbuf2也是空的,尝试从全局队列获取一个满的队列  
  13.         if wbuf.nobj == 0 {  
  14.             owbuf :wbuf  
  15.             wbuf = getfull()  
  16.             // 获取不到,则返回  
  17.             if wbuf == nil {  
  18.                 return 0  
  19.             }  
  20.             // 把空的队列上传到全局空队列,并把获取的满的队列,作为自身的wbuf1  
  21.             putempty(owbuf)  
  22.             w.wbuf1 = wbuf  
  23.         }  
  24.     }  
  25.     // TODO: This might be a good place to add prefetch code 
  26.     wbuf.nobj--  
  27.     return wbuf.obj[wbuf.nobj]  

gcw.tryGet() gcw.tryGetFast() 逻辑差不多,相对比较简单,就不继续分析了

scanobject

我们继续分析到 gcDrain 的L76,这里已经获取到了b,开始消费队列 

  1. func scanobject(b uintptr, gcw *gcWork) {  
  2.     // Find the bits for b and the size of the object at b.  
  3.     //  
  4.     // b is either the beginning of an object, in which case this  
  5.     // is the size of the object to scan, or it points to an  
  6.     // oblet, in which case we compute the size to scan below.  
  7.     // 获取b对应的bits  
  8.     hbits :heapBitsForAddr(b)  
  9.     // 获取b所在的span  
  10.     s :spanOfUnchecked(b)  
  11.     n :s.elemsize  
  12.     if n == 0 {  
  13.         throw("scanobject n == 0")  
  14.     }  
  15.     // 对象过大,则切割后再扫描,maxObletBytes为128k  
  16.     if n > maxObletBytes {  
  17.         // Large object. Break into oblets for better  
  18.         // parallelism and lower latency.  
  19.         if b == s.base() {  
  20.             // It's possible this is a noscan object (not  
  21.             // from greyobject, but from other code  
  22.             // paths), in which case we must *not* enqueue  
  23.             // oblets since their bitmaps will be  
  24.             // uninitialized.  
  25.             // 如果不是指针,直接标记返回,相当于标黑了  
  26.             if s.spanclass.noscan() {  
  27.                 // Bypass the whole scan.  
  28.                 gcw.bytesMarked += uint64(n)  
  29.                 return  
  30.             }  
  31.             // Enqueue the other oblets to scan later.  
  32.             // Some oblets may be in b's scalar tail, but  
  33.             // these will be marked as "no more pointers",  
  34.             // so we'll drop out immediately when we go to  
  35.             // scan those.  
  36.             // 按maxObletBytes切割后放入到 队列  
  37.             for oblet :b + maxObletBytes; oblet < s.base()+s.elemsize; oblet += maxObletBytes {  
  38.                 if !gcw.putFast(oblet) {  
  39.                     gcw.put(oblet)  
  40.                 }  
  41.             }  
  42.         }  
  43.         // Compute the size of the oblet. Since this object  
  44.         // must be a large object, s.base() is the beginning  
  45.         // of the object.  
  46.         n = s.base() + s.elemsize - b  
  47.         if n > maxObletBytes {  
  48.             n = maxObletBytes  
  49.         }  
  50.     }  
  51.     var i uintptr  
  52.     for i = 0; i < n; i += sys.PtrSize {  
  53.         // Find bits for this word.  
  54.         // 获取到对应的bits  
  55.         if i != 0 {  
  56.             // Avoid needless hbits.next() on last iteration.  
  57.             hbitshbits = hbits.next()  
  58.         }  
  59.         // Load bits once. See CL 22712 and issue 16973 for discussion.  
  60.         bits :hbits.bits()  
  61.         // During checkmarking, 1-word objects store the checkmark  
  62.         // in the type bit for the one word. The only one-word objects  
  63.         // are pointers, or else they'd be merged with other non-pointer  
  64.         // data into larger allocations.  
  65.         if i != 1*sys.PtrSize && bits&bitScan == 0 {  
  66.             break // no more pointers in this object  
  67.         }  
  68.         // 不是指针,继续  
  69.         if bits&bitPointer == 0 {  
  70.             continue // not a pointer  
  71.         }  
  72.         // Work here is duplicated in scanblock and above.  
  73.         // If you make changes here, make changes there too.  
  74.         obj := *(*uintptr)(unsafe.Pointer(b + i))  
  75.         // At this point we have extracted the next potential pointer.  
  76.         // Quickly filter out nil and pointers back to the current object.  
  77.         if obj != 0 && obj-b >= n {  
  78.             // Test if obj points into the Go heap and, if so,  
  79.             // mark the object.  
  80.             //  
  81.             // Note that it's possible for findObject to  
  82.             // fail if obj points to a just-allocated heap  
  83.             // object because of a race with growing the  
  84.             // heap. In this case, we know the object was  
  85.             // just allocated and hence will be marked by  
  86.             // allocation itself.  
  87.             // 找到指针对应的对象,并标灰  
  88.             if obj, span, objIndex :findObject(obj, b, i); obj != 0 {  
  89.                 greyobject(obj, b, i, span, gcw, objIndex)  
  90.             }  
  91.         }  
  92.     }  
  93.     gcw.bytesMarked += uint64(n)  
  94.     gcw.scanWork += int64(i)  

综上,我们可以发现,标灰就是标记并放进队列,标黑就是标记,所以当灰色对象从队列中取出后,我们就可以认为这个对象是黑色对象了

至此,gcDrain的标记工作分析完成,我们继续回到gcBgMarkWorker分析

gcMarkDone

gcMarkDone会将mark1阶段进入到mark2阶段, mark2阶段进入到mark termination阶段

mark1阶段: 包括所有root标记,全局缓存队列和本地缓存队列

mark2阶段:本地缓存队列会被禁用 

  1. func gcMarkDone() {  
  2. top:  
  3.     semacquire(&work.markDoneSema)  
  4.     // Re-check transition condition under transition lock.  
  5.     if !(gcphase == _GCmark && work.nwait == work.nproc && !gcMarkWorkAvailable(nil)) {  
  6.         semrelease(&work.markDoneSema)  
  7.         return  
  8.     }  
  9.     // Disallow starting new workers so that any remaining workers  
  10.     // in the current mark phase will drain out.  
  11.     //  
  12.     // TODO(austin): Should dedicated workers keep an eye on this  
  13.     // and exit gcDrain promptly?  
  14.     // 禁止新的标记任务  
  15.     atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, -0xffffffff)  
  16.     prevFractionalGoal :gcController.fractionalUtilizationGoal  
  17.     gcController.fractionalUtilizationGoal = 0  
  18.     // 如果gcBlackenPromptly表名需要所有本地缓存队列立即上交到全局队列,并禁用本地缓存队列  
  19.     if !gcBlackenPromptly {  
  20.         // Transition from mark 1 to mark 2.  
  21.         //  
  22.         // The global work list is empty, but there can still be work  
  23.         // sitting in the per-P work caches.  
  24.         // Flush and disable work caches.  
  25.         // Disallow caching workbufs and indicate that we're in mark 2.  
  26.         // 禁用本地缓存队列,进入mark2阶段  
  27.         gcBlackenPromptly = true  
  28.         // Prevent completion of mark 2 until we've flushed  
  29.         // cached workbufs.  
  30.         atomic.Xadd(&work.nwait, -1)  
  31.         // GC is set up for mark 2. Let Gs blocked on the  
  32.         // transition lock go while we flush caches.  
  33.         semrelease(&work.markDoneSema)  
  34.         // 切换到g0执行,本地缓存上传到全局的操作  
  35.         systemstack(func() {  
  36.             // Flush all currently cached workbufs and  
  37.             // ensure all Ps see gcBlackenPromptly. This  
  38.             // also blocks until any remaining mark 1  
  39.             // workers have exited their loop so we can  
  40.             // start new mark 2 workers.  
  41.             forEachP(func(_p_ *p) {  
  42.                 wbBufFlush1(_p_)  
  43.                 _p_.gcw.dispose()  
  44.             })  
  45.         })  
  46.         // Check that roots are marked. We should be able to  
  47.         // do this before the forEachP, but based on issue  
  48.         // #16083 there may be a (harmless) race where we can  
  49.         // enter mark 2 while some workers are still scanning  
  50.         // stacks. The forEachP ensures these scans are done.  
  51.         //  
  52.         // TODO(austin): Figure out the race and fix this  
  53.         // properly.  
  54.         // 检查所有的root是否都被标记了  
  55.         gcMarkRootCheck()  
  56.         // Now we can start up mark 2 workers.  
  57.         atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, 0xffffffff)  
  58.         gcController.fractionalUtilizationGoal = prevFractionalGoal  
  59.         incnwait :atomic.Xadd(&work.nwait, +1)  
  60.         // 如果没有更多的任务,则执行第二次调用,从mark2阶段转换到mark termination阶段  
  61.         if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {  
  62.             // This loop will make progress because  
  63.             // gcBlackenPromptly is now true, so it won't  
  64.             // take this same "if" branch.  
  65.             goto top  
  66.         }  
  67.     } else {  
  68.         // Transition to mark termination.  
  69.         now :nanotime()  
  70.         work.tMarkTerm = now  
  71.         work.pauseStart = now  
  72.         getg().m.preemptoff = "gcing"  
  73.         if trace.enabled {  
  74.             traceGCSTWStart(0)  
  75.         }  
  76.         systemstack(stopTheWorldWithSema)  
  77.         // The gcphase is _GCmark, it will transition to _GCmarktermination  
  78.         // below. The important thing is that the wb remains active until  
  79.         // all marking is complete. This includes writes made by the GC.  
  80.         // Record that one root marking pass has completed.  
  81.         work.markrootDone = true  
  82.         // Disable assists and background workers. We must do  
  83.         // this before waking blocked assists.  
  84.         atomic.Store(&gcBlackenEnabled, 0)  
  85.         // Wake all blocked assists. These will run when we  
  86.         // start the world again.  
  87.         // 唤醒所有的辅助GC  
  88.         gcWakeAllAssists()  
  89.         // Likewise, release the transition lock. Blocked  
  90.         // workers and assists will run when we start the  
  91.         // world again.  
  92.         semrelease(&work.markDoneSema)  
  93.         // endCycle depends on all gcWork cache stats being  
  94.         // flushed. This is ensured by mark 2.  
  95.         // 计算下一次gc出发的阈值  
  96.         nextTriggerRatio :gcController.endCycle()  
  97.         // Perform mark termination. This will restart the world.  
  98.         // start the world,并进入完成阶段  
  99.         gcMarkTermination(nextTriggerRatio)  
  100.     }  

gcMarkTermination

结束标记,并进行清扫等工作 

  1. func gcMarkTermination(nextTriggerRatio float64) {  
  2.     // World is stopped.  
  3.     // Start marktermination which includes enabling the write barrier.  
  4.     atomic.Store(&gcBlackenEnabled, 0)  
  5.     gcBlackenPromptly = false  
  6.     // 设置GC的阶段标识  
  7.     setGCPhase(_GCmarktermination)  
  8.     work.heap1 = memstats.heap_live  
  9.     startTime :nanotime()  
  10.     mp :acquirem()  
  11.     mp.preemptoff = "gcing"  
  12.     _g_ :getg()  
  13.     _g_.m.traceback = 2  
  14.     gp :_g_.m.curg  
  15.     // 设置当前g的状态为waiting状态  
  16.     casgstatus(gp, _Grunning, _Gwaiting)  
  17.     gp.waitreason = waitReasonGarbageCollection  
  18.     // Run gc on the g0 stack. We do this so that the g stack  
  19.     // we're currently running on will no longer change. Cuts  
  20.     // the root set down a bit (g0 stacks are not scanned, and  
  21.     // we don't need to scan gc's internal state).  We also  
  22.     // need to switch to g0 so we can shrink the stack.  
  23.     systemstack(func() {  
  24.         // 通过g0扫描当前g的栈  
  25.         gcMark(startTime)  
  26.         // Must return immediately.  
  27.         // The outer function's stack may have moved  
  28.         // during gcMark (it shrinks stacks, including the  
  29.         // outer function's stack), so we must not refer  
  30.         // to any of its variables. Return back to the  
  31.         // non-system stack to pick up the new addresses  
  32.         // before continuing.  
  33.     })  
  34.     systemstack(func() {  
  35.         workwork.heap2 = work.bytesMarked  
  36.         if debug.gccheckmark > 0 {  
  37.             // Run a full stop-the-world mark using checkmark bits,  
  38.             // to check that we didn't forget to mark anything during  
  39.             // the concurrent mark process.  
  40.             // 如果启用了gccheckmark,则检查所有可达对象是否都有标记  
  41.             gcResetMarkState()  
  42.             initCheckmarks()  
  43.             gcMark(startTime)  
  44.             clearCheckmarks()  
  45.         }  
  46.         // marking is complete so we can turn the write barrier off  
  47.         // 设置gc的阶段标识,GCoff时会关闭写屏障  
  48.         setGCPhase(_GCoff)  
  49.         // 开始清扫  
  50.         gcSweep(work.mode)  
  51.         if debug.gctrace > 1 {  
  52.             startTime = nanotime()  
  53.             // The g stacks have been scanned so  
  54.             // they have gcscanvalid==true and gcworkdone==true.  
  55.             // Reset these so that all stacks will be rescanned.  
  56.             gcResetMarkState()  
  57.             finishsweep_m()  
  58.             // Still in STW but gcphase is _GCoff, reset to _GCmarktermination  
  59.             // At this point all objects will be found during the gcMark which  
  60.             // does a complete STW mark and object scan.  
  61.             setGCPhase(_GCmarktermination)  
  62.             gcMark(startTime)  
  63.             setGCPhase(_GCoff) // marking is done, turn off wb.  
  64.             gcSweep(work.mode)  
  65.         }  
  66.     })  
  67.     _g_.m.traceback = 0  
  68.     casgstatus(gp, _Gwaiting, _Grunning)  
  69.     if trace.enabled {  
  70.         traceGCDone()  
  71.     }  
  72.     // all done  
  73.     mp.preemptoff = ""  
  74.     if gcphase != _GCoff {  
  75.         throw("gc done but gcphase != _GCoff")  
  76.     }  
  77.     // Update GC trigger and pacing for the next cycle.  
  78.     // 更新下次出发gc的增长比  
  79.     gcSetTriggerRatio(nextTriggerRatio)  
  80.     // Update timing memstats  
  81.     // 更新用时  
  82.     now :nanotime()  
  83.     sec, nsec, _ :time_now()  
  84.     unixNow :sec*1e9 + int64(nsec)  
  85.     work.pauseNS += now - work.pauseStart  
  86.     work.tEnd = now  
  87.     atomic.Store64(&memstats.last_gc_unix, uint64(unixNow)) // must be Unix time to make sense to user  
  88.     atomic.Store64(&memstats.last_gc_nanotime, uint64(now)) // monotonic time for us  
  89.     memstats.pause_ns[memstats.numgc%uint32(len(memstats.pause_ns))] = uint64(work.pauseNS)  
  90.     memstats.pause_end[memstats.numgc%uint32(len(memstats.pause_end))] = uint64(unixNow)  
  91.     memstats.pause_total_ns += uint64(work.pauseNS)  
  92.     // Update work.totaltime.  
  93.     sweepTermCpu :int64(work.stwprocs) * (work.tMark - work.tSweepTerm)  
  94.     // We report idle marking time below, but omit it from the  
  95.     // overall utilization here since it's "free".  
  96.     markCpu :gcController.assistTime + gcController.dedicatedMarkTime + gcController.fractionalMarkTime  
  97.     markTermCpu :int64(work.stwprocs) * (work.tEnd - work.tMarkTerm)  
  98.     cycleCpu :sweepTermCpu + markCpu + markTermCpu  
  99.     work.totaltime += cycleCpu  
  100.     // Compute overall GC CPU utilization.  
  101.     totalCpu :sched.totaltime + (now-sched.procresizetime)*int64(gomaxprocs)  
  102.     memstats.gc_cpu_fraction = float64(work.totaltime) / float64(totalCpu)  
  103.     // Reset sweep state.  
  104.     // 重置清扫的状态  
  105.     sweep.nbgsweep = 0  
  106.     sweep.npausesweep = 0  
  107.     // 如果是强制开启的gc,标识增加  
  108.     if work.userForced {  
  109.         memstats.numforcedgc++  
  110.     }  
  111.     // Bump GC cycle count and wake goroutines waiting on sweep.  
  112.     // 统计执行GC的次数然后唤醒等待清扫的G  
  113.     lock(&work.sweepWaiters.lock)  
  114.     memstats.numgc++  
  115.     injectglist(work.sweepWaiters.head.ptr())  
  116.     work.sweepWaiters.head = 0  
  117.     unlock(&work.sweepWaiters.lock)  
  118.     // Finish the current heap profiling cycle and start a new  
  119.     // heap profiling cycle. We do this before starting the world  
  120.     // so events don't leak into the wrong cycle.  
  121.     mProf_NextCycle()  
  122.     // start the world  
  123.     systemstack(func() { startTheWorldWithSema(true) })  
  124.     // Flush the heap profile so we can start a new cycle next GC.  
  125.     // This is relatively expensive, so we don't do it with the  
  126.     // world stopped.  
  127.     mProf_Flush()  
  128.     // Prepare workbufs for freeing by the sweeper. We do this  
  129.     // asynchronously because it can take non-trivial time.  
  130.     prepareFreeWorkbufs()  
  131.     // Free stack spans. This must be done between GC cycles.  
  132.     systemstack(freeStackSpans)  
  133.     // Print gctrace before dropping worldsema. As soon as we drop  
  134.     // worldsema another cycle could start and smash the stats  
  135.     // we're trying to print.  
  136.     if debug.gctrace > 0 {  
  137.         util :int(memstats.gc_cpu_fraction * 100)  
  138.         var sbuf [24]byte  
  139.         printlock()  
  140.         print("gc ", memstats.numgc,  
  141.             " @", string(itoaDiv(sbuf[:], uint64(work.tSweepTerm-runtimeInitTime)/1e6, 3)), "s ",  
  142.             util, "%: ")  
  143.         prev :work.tSweepTerm  
  144.         for i, ns :range []int64{work.tMark, work.tMarkTerm, work.tEnd} {  
  145.             if i != 0 {  
  146.                 print("+")  
  147.             }  
  148.             print(string(fmtNSAsMS(sbuf[:], uint64(ns-prev))))  
  149.             prev = ns  
  150.         }  
  151.         print(" ms clock, ")  
  152.         for i, ns :range []int64{sweepTermCpu, gcController.assistTime, gcController.dedicatedMarkTime + gcController.fractionalMarkTime, gcController.idleMarkTime, markTermCpu} { 
  153.              if i == 2 || i == 3 {  
  154.                 // Separate mark time components with /.  
  155.                 print("/")  
  156.             } else if i != 0 {  
  157.                 print("+")  
  158.             }  
  159.             print(string(fmtNSAsMS(sbuf[:], uint64(ns))))  
  160.         }  
  161.         print(" ms cpu, ",  
  162.             work.heap0>>20, "->", work.heap1>>20, "->", work.heap2>>20, " MB, ",  
  163.             work.heapGoal>>20, " MB goal, ",  
  164.             work.maxprocs, " P")  
  165.         if work.userForced {  
  166.             print(" (forced)")  
  167.         }  
  168.         print("\n")  
  169.         printunlock()  
  170.     }  
  171.     semrelease(&worldsema)  
  172.     // Careful: another GC cycle may start now.  
  173.     releasem(mp)  
  174.     mp = nil  
  175.     // now that gc is done, kick off finalizer thread if needed  
  176.     // 如果不是并行GC,则让当前M开始调度  
  177.     if !concurrentSweep {  
  178.         // give the queued finalizers, if any, a chance to run  
  179.         Gosched()  
  180.     }  

goSweep

清扫任务 

  1. func gcSweep(mode gcMode) {  
  2.     if gcphase != _GCoff {  
  3.         throw("gcSweep being done but phase is not GCoff")  
  4.     }  
  5.     lock(&mheap_.lock)  
  6.     // sweepgen在每次GC之后都会增长2,每次GC之后sweepSpans的角色都会互换  
  7.     mheap_.sweepgen += 2  
  8.     mheap_.sweepdone = 0  
  9.     if mheap_.sweepSpans[mheap_.sweepgen/2%2].index != 0 {  
  10.         // We should have drained this list during the last  
  11.         // sweep phase. We certainly need to start this phase  
  12.         // with an empty swept list.  
  13.         throw("non-empty swept list")  
  14.     }  
  15.     mheap_.pagesSwept = 0  
  16.     unlock(&mheap_.lock)  
  17.     // 如果不是并行GC,或者强制GC  
  18.     if !_ConcurrentSweep || mode == gcForceBlockMode {  
  19.         // Special case synchronous sweep.  
  20.         // Record that no proportional sweeping has to happen.  
  21.         lock(&mheap_.lock)  
  22.         mheap_.sweepPagesPerByte = 0  
  23.         unlock(&mheap_.lock)  
  24.         // Sweep all spans eagerly.  
  25.         // 清扫所有的span  
  26.         for sweepone() != ^uintptr(0) {  
  27.             sweep.npausesweep++  
  28.         }  
  29.         // Free workbufs eagerly.  
  30.         // 释放所有的 workbufs  
  31.         prepareFreeWorkbufs()  
  32.         for freeSomeWbufs(false) {  
  33.         }  
  34.         // All "free" events for this mark/sweep cycle have  
  35.         // now happened, so we can make this profile cycle  
  36.         // available immediately.  
  37.         mProf_NextCycle()  
  38.         mProf_Flush()  
  39.         return  
  40.     }  
  41.     // Background sweep.  
  42.     lock(&sweep.lock)  
  43.     // 唤醒后台清扫任务,也就是 bgsweep 函数,清扫流程跟上面非并行清扫差不多  
  44.     if sweep.parked {  
  45.         sweep.parked = false  
  46.         ready(sweep.g, 0, true)  
  47.     }  
  48.     unlock(&sweep.lock)  

sweepone

接下来我们就分析一下sweepone 清扫的流程 

  1. func sweepone() uintptr {  
  2.     _g_ :getg()  
  3.     sweepRatio :mheap_.sweepPagesPerByte // For debugging  
  4.     // increment locks to ensure that the goroutine is not preempted  
  5.     // in the middle of sweep thus leaving the span in an inconsistent state for next GC  
  6.     _g_.m.locks++  
  7.     // 检查是否已经完成了清扫  
  8.     if atomic.Load(&mheap_.sweepdone) != 0 {  
  9.         _g_.m.locks--  
  10.         return ^uintptr(0)  
  11.     }  
  12.     // 增加清扫的worker数量  
  13.     atomic.Xadd(&mheap_.sweepers, +1)  
  14.     npages := ^uintptr(0)  
  15.     sg :mheap_.sweepgen  
  16.     for {  
  17.         // 循环获取需要清扫的span  
  18.         s :mheap_.sweepSpans[1-sg/2%2].pop()  
  19.         if s == nil {  
  20.             atomic.Store(&mheap_.sweepdone, 1)  
  21.             break  
  22.         }  
  23.         if s.state != mSpanInUse {  
  24.             // This can happen if direct sweeping already  
  25.             // swept this span, but in that case the sweep  
  26.             // generation should always be up-to-date.  
  27.             if s.sweepgen != sg {  
  28.                 print("runtime: bad span s.state=", s.state, " s.sweepgen=", s.sweepgen, " sweepgen=", sg, "\n")  
  29.                 throw("non in-use span in unswept list")  
  30.             }  
  31.             continue  
  32.         }  
  33.         // sweepgen == h->sweepgen - 2, 表示这个span需要清扫  
  34.         // sweepgen == h->sweepgen - 1, 表示这个span正在被清扫  
  35.         // 这是里确定span的状态及尝试转换span的状态  
  36.         if s.sweepgen != sg-2 || !atomic.Cas(&s.sweepgen, sg-2, sg-1) {  
  37.             continue  
  38.         }  
  39.         npages = s.npages  
  40.         // 单个span的清扫  
  41.         if !s.sweep(false) {  
  42.             // Span is still in-use, so this returned no  
  43.             // pages to the heap and the span needs to  
  44.             // move to the swept in-use list.  
  45.             npages = 0  
  46.         }  
  47.         break  
  48.     }  
  49.     // Decrement the number of active sweepers and if this is the  
  50.     // last one print trace information.  
  51.     // 当前worker清扫任务完成,更新sweepers的数量  
  52.     if atomic.Xadd(&mheap_.sweepers, -1) == 0 && atomic.Load(&mheap_.sweepdone) != 0 {  
  53.         if debug.gcpacertrace > 0 {  
  54.             print("pacer: sweep done at heap size ", memstats.heap_live>>20, "MB; allocated ", (memstats.heap_live-mheap_.sweepHeapLiveBasis)>>20, "MB during sweep; swept ", mheap_.pagesSwept, " pages at ", sweepRatio, " pages/byte\n") 
  55.          }  
  56.     }  
  57.     _g_.m.locks--  
  58.     return npages  

mspan.sweep 

  1. func (s *mspan) sweep(preserve bool) bool {  
  2.     // It's critical that we enter this function with preemption disabled,  
  3.     // GC must not start while we are in the middle of this function.  
  4.     _g_ :getg()  
  5.     if _g_.m.locks == 0 && _g_.m.mallocing == 0 && _g_ != _g_.m.g0 {  
  6.         throw("MSpan_Sweep: m is not locked") 
  7.     }  
  8.     sweepgen :mheap_.sweepgen  
  9.     // 只有正在清扫中状态的span才可以正常执行  
  10.     if s.state != mSpanInUse || s.sweepgen != sweepgen-1 {  
  11.         print("MSpan_Sweep: state=", s.state, " sweepgen=", s.sweepgen, " mheap.sweepgen=", sweepgen, "\n")  
  12.         throw("MSpan_Sweep: bad span state")  
  13.     }  
  14.     if trace.enabled { 
  15.          traceGCSweepSpan(s.npages * _PageSize)  
  16.     }  
  17.     // 先更新清扫的page数  
  18.     atomic.Xadd64(&mheap_.pagesSwept, int64(s.npages))  
  19.     spc :s.spanclass  
  20.     size :s.elemsize  
  21.     res :false  
  22.     c :_g_.m.mcache  
  23.     freeToHeap :false  
  24.     // The allocBits indicate which unmarked objects don't need to be  
  25.     // processed since they were free at the end of the last GC cycle  
  26.     // and were not allocated since then.  
  27.     // If the allocBits index is >= s.freeindex and the bit  
  28.     // is not marked then the object remains unallocated  
  29.     // since the last GC.  
  30.     // This situation is analogous to being on a freelist.  
  31.     // Unlink & free special records for any objects we're about to free.  
  32.     // Two complications here:  
  33.     // 1. An object can have both finalizer and profile special records.  
  34.     //    In such case we need to queue finalizer for execution,  
  35.     //    mark the object as live and preserve the profile special.  
  36.     // 2. A tiny object can have several finalizers setup for different offsets.  
  37.     //    If such object is not marked, we need to queue all finalizers at once.  
  38.     // Both 1 and 2 are possible at the same time.  
  39.     specialp := &s.specials  
  40.     special := *specialp  
  41.     // 判断在special中的对象是否存活,是否至少有一个finalizer,释放没有finalizer的对象,把有finalizer的对象组成队列  
  42.     for special != nil {  
  43.         // A finalizer can be set for an inner byte of an object, find object beginning.  
  44.         objIndex :uintptr(special.offset) / size  
  45.         p :s.base() + objIndex*size  
  46.         mbits :s.markBitsForIndex(objIndex)  
  47.         if !mbits.isMarked() {  
  48.             // This object is not marked and has at least one special record.  
  49.             // Pass 1: see if it has at least one finalizer.  
  50.             hasFin :false  
  51.             endOffset :p - s.base() + size  
  52.             for tmp :special; tmp != nil && uintptr(tmp.offset) < endOffsettmptmp = tmp.next {  
  53.                 if tmp.kind == _KindSpecialFinalizer {  
  54.                     // Stop freeing of object if it has a finalizer.  
  55.                     mbits.setMarkedNonAtomic()  
  56.                     hasFin = true  
  57.                     break  
  58.                 }  
  59.             }  
  60.             // Pass 2: queue all finalizers _or_ handle profile record.  
  61.             for special != nil && uintptr(special.offset) < endOffset {  
  62.                 // Find the exact byte for which the special was setup  
  63.                 // (as opposed to object beginning).  
  64.                 p :s.base() + uintptr(special.offset)  
  65.                 if special.kind == _KindSpecialFinalizer || !hasFin {  
  66.                     // Splice out special record.  
  67.                     y :special  
  68.                     specialspecial = special.next  
  69.                     *specialspecialp = special 
  70.                      freespecial(y, unsafe.Pointer(p), size)  
  71.                 } else {  
  72.                     // This is profile record, but the object has finalizers (so kept alive).  
  73.                     // Keep special record.  
  74.                     specialp = &special.next  
  75.                     special = *specialp  
  76.                 }  
  77.             }  
  78.         } else {  
  79.             // object is still live: keep special record  
  80.             specialp = &special.next  
  81.             special = *specialp  
  82.         }  
  83.     }  
  84.     if debug.allocfreetrace != 0 || raceenabled || msanenabled {  
  85.         // Find all newly freed objects. This doesn't have to  
  86.         // efficient; allocfreetrace has massive overhead.  
  87.         mbits :s.markBitsForBase()  
  88.         abits :s.allocBitsForIndex(0)  
  89.         for i :uintptr(0); i < s.nelems; i++ {  
  90.             if !mbits.isMarked() && (abits.index < s.freeindex || abits.isMarked()) {  
  91.                 x :s.base() + i*s.elemsize  
  92.                 if debug.allocfreetrace != 0 {  
  93.                     tracefree(unsafe.Pointer(x), size)  
  94.                 }  
  95.                 if raceenabled {  
  96.                     racefree(unsafe.Pointer(x), size)  
  97.                 }  
  98.                 if msanenabled {  
  99.                     msanfree(unsafe.Pointer(x), size)  
  100.                 }  
  101.             }  
  102.             mbits.advance()  
  103.             abits.advance()  
  104.         }  
  105.     }  
  106.     // Count the number of free objects in this span.  
  107.     // 获取需要释放的alloc对象的总数  
  108.     nalloc :uint16(s.countAlloc())  
  109.     // 如果sizeclass为0,却分配的总数量为0,则释放到mheap  
  110.     if spc.sizeclass() == 0 && nalloc == 0 {  
  111.         s.needzero = 1  
  112.         freeToHeap = true  
  113.     }  
  114.     nfreed :s.allocCount - nalloc  
  115.     if nalloc > s.allocCount {  
  116.         print("runtime: nelems=", s.nelems, " nalloc=", nalloc, " previous allocCount=", s.allocCount, " nfreed=", nfreed, "\n")  
  117.         throw("sweep increased allocation count")  
  118.     }  
  119.     s.allocCount = nalloc  
  120.     // 判断span是否empty  
  121.     wasempty :s.nextFreeIndex() == s.nelems  
  122.     // 重置freeindex  
  123.     s.freeindex = 0 // reset allocation index to start of span.  
  124.     if trace.enabled {  
  125.         getg().m.p.ptr().traceReclaimed += uintptr(nfreed) * s.elemsize  
  126.     }  
  127.     // gcmarkBits becomes the allocBits.  
  128.     // get a fresh cleared gcmarkBits in preparation for next GC  
  129.     // 重置 allocBits为 gcMarkBits  
  130.     ss.allocBits = s.gcmarkBits  
  131.     // 重置 gcMarkBits  
  132.     s.gcmarkBits = newMarkBits(s.nelems)  
  133.     // Initialize alloc bits cache.  
  134.     // 更新allocCache  
  135.     s.refillAllocCache(0)  
  136.     // We need to set s.sweepgen = h.sweepgen only when all blocks are swept,  
  137.     // because of the potential for a concurrent free/SetFinalizer.  
  138.     // But we need to set it before we make the span available for allocation  
  139.     // (return it to heap or mcentral), because allocation code assumes that a  
  140.     // span is already swept if available for allocation.  
  141.     if freeToHeap || nfreed == 0 {  
  142.         // The span must be in our exclusive ownership until we update sweepgen,  
  143.         // check for potential races.  
  144.         if s.state != mSpanInUse || s.sweepgen != sweepgen-1 {  
  145.             print("MSpan_Sweep: state=", s.state, " sweepgen=", s.sweepgen, " mheap.sweepgen=", sweepgen, "\n")  
  146.             throw("MSpan_Sweep: bad span state after sweep")  
  147.         }  
  148.         // Serialization point.  
  149.         // At this point the mark bits are cleared and allocation ready  
  150.         // to go so release the span.  
  151.         atomic.Store(&s.sweepgen, sweepgen)  
  152.     }  
  153.     if nfreed > 0 && spc.sizeclass() != 0 {  
  154.         c.local_nsmallfree[spc.sizeclass()] += uintptr(nfreed)  
  155.         // 把span释放到mcentral上  
  156.         res = mheap_.central[spc].mcentral.freeSpan(s, preserve, wasempty)  
  157.         // MCentral_FreeSpan updates sweepgen  
  158.     } else if freeToHeap {  
  159.         // 这里是大对象的span释放,与117行呼应  
  160.         // Free large span to heap  
  161.         // NOTE(rsc,dvyukov): The original implementation of efence  
  162.         // in CL 22060046 used SysFree instead of SysFault, so that  
  163.         // the operating system would eventually give the memory  
  164.         // back to us again, so that an efence program could run  
  165.         // longer without running out of memory. Unfortunately, 
  166.          // calling SysFree here without any kind of adjustment of the  
  167.         // heap data structures means that when the memory does  
  168.         // come back to us, we have the wrong metadata for it, either in  
  169.         // the MSpan structures or in the garbage collection bitmap.  
  170.         // Using SysFault here means that the program will run out of  
  171.         // memory fairly quickly in efence mode, but at least it won't  
  172.         // have mysterious crashes due to confused memory reuse.  
  173.         // It should be possible to switch back to SysFree if we also  
  174.         // implement and then call some kind of MHeap_DeleteSpan.  
  175.         if debug.efence > 0 {  
  176.             s.limit = 0 // prevent mlookup from finding this span  
  177.             sysFault(unsafe.Pointer(s.base()), size)  
  178.         } else {  
  179.             // 把sapn释放到mheap上  
  180.             mheap_.freeSpan(s, 1)  
  181.         }  
  182.         c.local_nlargefree++  
  183.         c.local_largefree += size  
  184.         res = true  
  185.     }  
  186.     if !res {  
  187.         // The span has been swept and is still in-use, so put  
  188.         // it on the swept in-use list.  
  189.         // 如果span未释放到mcentral或mheap,表示span仍然处于in-use状态  
  190.         mheap_.sweepSpans[sweepgen/2%2].push(s) 
  191.     }  
  192.     return res  

ok,至此Go的GC流程已经分析完成了,结合最上面开始的图,可能会容易理解一点 

责任编辑:庞桂玉 来源: segmentfault
相关推荐

2020-09-27 07:32:18

V8

2010-09-26 14:08:41

Java垃圾回收

2017-08-17 15:40:08

大数据Python垃圾回收机制

2017-03-03 09:26:48

PHP垃圾回收机制

2010-09-25 15:33:19

JVM垃圾回收

2009-06-23 14:15:00

Java垃圾回收

2021-11-05 15:23:20

JVM回收算法

2010-09-16 15:10:24

JVM垃圾回收机制

2010-09-25 15:26:12

JVM垃圾回收

2011-07-04 16:48:56

JAVA垃圾回收机制GC

2021-05-27 21:47:12

Python垃圾回收

2015-06-04 09:38:39

Java垃圾回收机

2011-06-28 12:39:34

Java垃圾回收

2017-06-12 17:38:32

Python垃圾回收引用

2011-07-04 13:12:04

JavaScript

2011-01-18 14:06:58

JavaScriptweb

2009-12-09 17:28:34

PHP垃圾回收机制

2010-10-13 10:24:38

垃圾回收机制JVMJava

2021-12-07 08:01:33

Javascript 垃圾回收机制前端

2011-12-26 09:50:05

.NET垃圾回收
点赞
收藏

51CTO技术栈公众号