深入理解Go-垃圾回收机制 - Go语言中文社区

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


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.     // 扫描只读的全局队列  <
版权声明:本文来源51CTO,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:http://developer.51cto.com/art/201908/601500.htm
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-15 22:40:58
  • 阅读 ( 1846 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢