深入Go底层原理剖析和源码解读,重写Redis中间件实战积累大型项目经验

发布时间 2023-11-07 16:16:43作者: 天使angl

Go 中的runtime 类似 Java的虚拟机,它负责管理包括内存分配、垃圾回收、栈处理、goroutine、channel、切片(slice)、map 和反射(reflection)等。Go 的可执行文件都比相对应的源代码文件要大很多,这是因为 Go 的 runtime 嵌入到了每一个可执行文件当中。

常见的几种gc算法:

引用计数:对每个对象维护一个引用计数,当引用该对象的对象被销毁时,引用计数减1,当引用计数器为0是回收该对象。

优点:对象可以很快的被回收,不会出现内存耗尽或达到某个阀值时才回收。

缺点:不能很好的处理循环引用,而且实时维护引用计数,有也一定的代价。

代表语言:Python、PHP、Swift

标记-清除:从根变量开始遍历所有引用的对象,引用的对象标记为"被引用",没有被标记的进行回收。

优点:解决了引用计数的缺点。

缺点:需要STW,即要暂时停掉程序运行。

代表语言:Golang(其采用三色标记法)

分代收集:按照对象生命周期长短划分不同的代空间,生命周期长的放入老年代,而短的放入新生代,不同代有不能的回收算法和回收频率。

优点:回收性能好

缺点:算法复杂

代表语言: JAVA

每种算法都不是完美的,都是折中的产物。

Gc流程图:

Stack scan:收集根对象(全局变量,和G stack),开启写屏障。全局变量、开启写屏障需要STW,G stack只需要停止该G就好,时间比较少。

 Mark: 扫描所有根对象, 和根对象可以到达的所有对象, 标记它们不被回收

Mark Termination: 完成标记工作, 重新扫描部分根对象(要求STW)

Sweep: 按标记结果清扫span

从上图中我们可以看到整个GC流程会进行两次STW(Stop The World), 第一次是Mark阶段的开始, 第二次是Mark Termination阶段.
第一次STW会准备根对象的扫描, 启动写屏障(Write Barrier)和辅助GC(mutator assist).
第二次STW会重新扫描部分根对象, 禁用写屏障(Write Barrier)和辅助GC(mutator assist).
需要注意的是, 不是所有根对象的扫描都需要STW, 例如扫描栈上的对象只需要停止拥有该栈的G.

三色标记

有黑、灰、白三个集合,每种颜色的含义:

白色:对象未被标记,gcmarkBits对应的位为0

灰色:对象已被标记,但这个对象包含的子对象未标记,gcmarkBits对应的位为1

黑色:对象已被标记,且这个对象包含的子对象也已标记,gcmarkBits对应的位为1

灰色和黑色的gcmarkBits都是1,如何区分二者呢?

标记任务有标记队列,在标记队列中的是灰色,不在标记队里中的是黑色。标记过程见下图:

 

上图中根对象A是栈上分配的对象,H是堆中分配的全局变量,根对象A、H内部有分别引用了其他对象,而其他对象内部可能还引用额其他对象,各个对象见的关系如上图所示。

  1. 初始状态下所有对象都是白色的。
  2. 接着开始扫描根对象,A、H是根对象所以被扫描到,A,H变为灰色对象。
  3. 接下来就开始扫描灰色对象,通过A到达B,B被标注灰色,A扫描结束后被标注黑色。同理J,K都被标注灰色,H被标注黑色。
  4. 继续扫描灰色对象,通过B到达C,C 被标注灰色,B被标注黑色,因为J,K没有引用对象,J,K标注黑色结束
  5. 最终,黑色的对象会被保留下来,白色对象D,E,F会被回收掉。

屏障

                         

上图,假如B对象变黑后,又给B指向对象G,因为这个时候G对象已经扫描过了,所以G 对象还是白色,会被误回收。怎么解决这个问题呢?

最简单的方法就是STW(stop the world)。也就是说,停止所有的协程。这个方法比较暴力会引起程序的卡顿,并不友好。让GC回收器,满足下面两种情况之一时,可保对象不丢失. 所以引出强-弱三色不变式:

强三色不变式:黑色不能引用白色对象。

弱三色不变式:被黑色引用的白色对象都处于灰色保护。

如何实现这个两个公式呢?这就是屏障机制。

GO1.5 采用了插入屏障、删除屏障。到了GO1.8采用混合屏障。黑色对象的内存槽有两种位置, . 栈空间的特点是容量小,但是要求相应速度快,因为函数调用弹出频繁使用, 所以“插入屏障”机制,在栈空间的对象操作中不使用. 而仅仅使用在堆空间对象的操作中。

插入屏障:插入屏障只对堆上的内存分配起作用,栈空间先扫描一遍然后启动STW后再重新扫描一遍扫描后停止STW。如果在对象在插入平展期间分配内存会自动设置成灰色,不用再重新扫描。

删除屏障:删除屏障适用于栈和堆,在删除屏障机制下删除一个节点该节点会被置成灰色,后续会继续扫描该灰色对象的子对象。该方法就是精准度不够高

混合屏障:

插入写屏障和删除写屏障的短板:

插入写屏障:结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活;

删除写屏障:回收精度低,GC开始时STW扫描堆栈来记录初始快照,这个过程会保护开始时刻的所有存活对象。

混合写屏障规则

具体操作:

1、GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW),

2、GC期间,任何在栈上创建的新对象,均为黑色。

3、被删除的对象标记为灰色。

4、被添加的对象标记为灰色。

满足: 变形的弱三色不变式.

伪代码如下:

  1.  
    添加下游对象(当前下游对象slot, 新下游对象ptr) {
  2.  
    //1
  3.  
    标记灰色(当前下游对象slot) //只要当前下游对象被移走,就标记灰色
  4.  
     
  5.  
    //2
  6.  
    标记灰色(新下游对象ptr)
  7.  
     
  8.  
    //3
  9.  
    当前下游对象slot = 新下游对象ptr
  10.  
    }

上面说到整个GC有两次STW,采用混合屏障后可以大幅压缩第二次STW的时间。

Gc pacer

触发gc的时机:

阈值gcTriggerHeap:默认内存扩大一倍,启动gc

定期gcTriggerTime:默认2min触发一次gc,src/runtime/proc.go:forcegcperiod

手动gcTriggerCycle:runtime.gc()

当然了阀值是根据使用内存的增加动态变化的。假如前一次GC之后内存使用Hm(n-1)为1GB,默认GCGO=100,那么下一次会在接近Hg(2GB)的位置发起新一轮的GC。如下图:

Ht的时候开始GC,Ha的时候结束GC,Ha非常接近Hg。

(1)如何保证在Ht开始gc时所有的span都被清扫完?

除了有一个后台清扫协程外,用户的分配内存时也需要辅助清扫来保证在开启下一轮的gc时span都被清扫完毕。假设有k page的span需要sweep,那么距离下一次gc还有Ht-Hm(n-1)的内存可供分配,那么平均每申请1byte内存需要清扫k/ Ht-Hm(n-1) page 的span。(k值会根据sweep进度更改)

辅助清扫申请新span时才会检查,,辅助清扫的触发可以看cacheSpan函数, 触发时G会帮助回收"工作量"页的对象, 工作量的计算公式是:

spanBytes * sweepPagesPerByte
 

意思是分配的大小乘以系数sweepPagesPerByte, sweepPagesPerByte的计算在函数gcSetTriggerRatio中, 公式是:

  1.  
    // 当前的Heap大小
  2.  
    heapLiveBasis := atomic.Load64(&memstats.heap_live)
  3.  
    // 距离触发GC的Heap大小 = 下次触发GC的Heap大小 - 当前的Heap大小
  4.  
    heapDistance := int64(trigger) - int64(heapLiveBasis)
  5.  
    heapDistance -= 1024 * 1024
  6.  
    if heapDistance < _PageSize {
  7.  
    heapDistance = _PageSize
  8.  
    }
  9.  
    // 已清扫的页数
  10.  
    pagesSwept := atomic.Load64(&mheap_.pagesSwept)
  11.  
    // 未清扫的页数 = 使用中的页数 - 已清扫的页数
  12.  
    sweepDistancePages := int64(mheap_.pagesInUse) - int64(pagesSwept)
  13.  
    if sweepDistancePages <= 0 {
  14.  
    mheap_.sweepPagesPerByte = 0
  15.  
    } else {
  16.  
    // 每分配1 byte(的span)需要辅助清扫的页数 = 未清扫的页数 / 距离触发GC的Heap大小
  17.  
    mheap_.sweepPagesPerByte = float64(sweepDistancePages) / float64(heapDistance)
  18.  
    }

 

(2)如何保证在Ha时gc都被mark完?

Gc在Ht开始,在到达Hg时尽量标记完所有的对象,除了后台的标记协程外还需要在分配内存是进行辅助mark。从Ht到Hg的内存可以分配,这个时候还有scanWorkExpected的对象需要scan,那么平均分配1byte内存需要辅助mark量:scanWorkExpected/(Hg-Ht) 个对象,scanWorkExpected会根据mark进度更改。

辅助标记的触发可以查看上面的mallocgc函数, 触发时G会帮助扫描"工作量"个对象, 工作量的计算公式是:

debtBytes * assistWorkPerByte
 

意思是分配的大小乘以系数assistWorkPerByte, assistWorkPerByte的计算在函数revise中, 公式是:

  1.  
    // 等待扫描的对象数量 = 未扫描的对象数量 - 已扫描的对象数量
  2.  
    scanWorkExpected := int64(memstats.heap_scan) - c.scanWork
  3.  
    if scanWorkExpected < 1000 {
  4.  
    scanWorkExpected = 1000
  5.  
    }
  6.  
    // 距离触发GC的Heap大小 = 期待触发GC的Heap大小 - 当前的Heap大小
  7.  
    // 注意next_gc的计算跟gc_trigger不一样, next_gc等于heap_marked * (1 + gcpercent / 100)
  8.  
    heapDistance := int64(memstats.next_gc) - int64(atomic.Load64(&memstats.heap_live))
  9.  
    if heapDistance <= 0 {
  10.  
    heapDistance = 1
  11.  
    }
  12.  
    // 每分配1 byte需要辅助扫描的对象数量 = 等待扫描的对象数量 / 距离触发GC的Heap大小
  13.  
    c.assistWorkPerByte = float64(scanWorkExpected) / float64(heapDistance)
  14.  
    c.assistBytesPerWork = float64(heapDistance) / float64(scanWorkExpected)

 根对象

在GC的标记阶段首先需要标记的就是"根对象", 从根对象开始可到达的所有对象都会被认为是存活的.
根对象包含了全局变量, 各个G的栈上的变量等, GC会先扫描根对象然后再扫描根对象可到达的所有对象.

Fixed Roots: 特殊的扫描工作 :

fixedRootFinalizers: 扫描析构器队列

fixedRootFreeGStacks: 释放已中止的G的栈

Flush Cache Roots: 释放mcache中的所有span, 要求STW

Data Roots: 扫描可读写的全局变量

BSS Roots: 扫描只读的全局变量

Span Roots: 扫描各个span中特殊对象(析构器列表)

Stack Roots: 扫描各个G的栈

标记阶段(Mark)会做其中的"Fixed Roots", "Data Roots", "BSS Roots", "Span Roots", "Stack Roots".
完成标记阶段(Mark Termination)会做其中的"Fixed Roots", "Flush Cache Roots".

对象扫描

当拿到一个对象的p时如何找到该对象的span和heapbit。以下分析是基于go1.10

我们在内存分配部分介绍过2 bit表示一个字,一个字节就可以表示4个字。2bit中一个表示是否被scan另一个表示该对象内是否有指针类型,根据地址p可以根据固定偏移计算出该p对应的hbit:

  1.  
    func heapBitsForAddr(addr uintptr) heapBits {
  2.  
    // 2 bits per work, 4 pairs per byte, and a mask is hard coded.
  3.  
    off := (addr - mheap_.arena_start) / sys.PtrSize
  4.  
    return heapBits{(*uint8)(unsafe.Pointer(mheap_.bitmap - off/4 - 1)), uint32(off & 3)}
  5.  
    }

查找p对应的span更简单了,我们前面介绍过spans区域中就是记录每个page对应的span结构,所以根据p对page取余计算出是第几个page就可以找到对应的span指针了

mheap_.spans[(p-mheap_.arena_start)>>_PageShift]
 

以下分析是基于go1.11及之后

Go1.11及以后的版本改用了稀疏索引的方式来管理整体的内存. 可以超过 512G 内存, 也可以允许内存空间扩展时不连续.在全局的 mheap struct 中有个 arenas 二阶数组, 在 linux amd64 上,一阶只有一个 slot, 二阶有 4M 个 slot, 每个 slot 指向一个 heapArena 结构, 每个 heapArena 结构可以管理 64M 内存, 所以在新的版本中, go 可以管理 4M*64M=256TB 内存, 即目前 64 位机器中 48bit 的寻址总线全部 256TB 内存。可以通过指针加上一定得偏移量, 就知道属于哪个 heap arean 64M 块. 再通过对 64M 求余, 结合 spans 数组, 即可知道属于哪个 mspan 了,结合 heapArean 的 bitmap 和每 8 个字节在 heapArean 中的偏移, 就可知道对象每 8 个字节是指针还是普通数据。

源码分析

源码分析引自:https://www.cnblogs.com/zkweb/p/7880099.html 讲的很详细:

 go触发gc会从gcStart函数开始:

  1.  
    // gcStart transitions the GC from _GCoff to _GCmark (if
  2.  
    // !mode.stwMark) or _GCmarktermination (if mode.stwMark) by
  3.  
    // performing sweep termination and GC initialization.
  4.  
    //
  5.  
    // This may return without performing this transition in some cases,
  6.  
    // such as when called on a system stack or with locks held.
  7.  
    func gcStart(mode gcMode, trigger gcTrigger) {
  8.  
    // 判断当前G是否可抢占, 不可抢占时不触发GC
  9.  
    // Since this is called from malloc and malloc is called in
  10.  
    // the guts of a number of libraries that might be holding
  11.  
    // locks, don't attempt to start GC in non-preemptible or
  12.  
    // potentially unstable situations.
  13.  
    mp := acquirem()
  14.  
    if gp := getg(); gp == mp.g0 || mp.locks > 1 || mp.preemptoff != "" {
  15.  
    releasem(mp)
  16.  
    return
  17.  
    }
  18.  
    releasem(mp)
  19.  
    mp = nil
  20.  
     
  21.  
    // 并行清扫上一轮GC未清扫的span
  22.  
    // Pick up the remaining unswept/not being swept spans concurrently
  23.  
    //
  24.  
    // This shouldn't happen if we're being invoked in background
  25.  
    // mode since proportional sweep should have just finished
  26.  
    // sweeping everything, but rounding errors, etc, may leave a
  27.  
    // few spans unswept. In forced mode, this is necessary since
  28.  
    // GC can be forced at any point in the sweeping cycle.
  29.  
    //
  30.  
    // We check the transition condition continuously here in case
  31.  
    // this G gets delayed in to the next GC cycle.
  32.  
    for trigger.test() && gosweepone() != ^uintptr(0) {
  33.  
    sweep.nbgsweep++
  34.  
    }
  35.  
     
  36.  
    // 上锁, 然后重新检查gcTrigger的条件是否成立, 不成立时不触发GC
  37.  
    // Perform GC initialization and the sweep termination
  38.  
    // transition.
  39.  
    semacquire(&work.startSema)
  40.  
    // Re-check transition condition under transition lock.
  41.  
    if !trigger.test() {
  42.  
    semrelease(&work.startSema)
  43.  
    return
  44.  
    }
  45.  
     
  46.  
    // 记录是否强制触发, gcTriggerCycle是runtime.GC用的
  47.  
    // For stats, check if this GC was forced by the user.
  48.  
    work.userForced = trigger.kind == gcTriggerAlways || trigger.kind == gcTriggerCycle
  49.  
     
  50.  
    // 判断是否指定了禁止并行GC的参数
  51.  
    // In gcstoptheworld debug mode, upgrade the mode accordingly.
  52.  
    // We do this after re-checking the transition condition so
  53.  
    // that multiple goroutines that detect the heap trigger don't
  54.  
    // start multiple STW GCs.
  55.  
    if mode == gcBackgroundMode {
  56.  
    if debug.gcstoptheworld == 1 {
  57.  
    mode = gcForceMode
  58.  
    } else if debug.gcstoptheworld == 2 {
  59.  
    mode = gcForceBlockMode
  60.  
    }
  61.  
    }
  62.  
     
  63.  
    // Ok, we're doing it! Stop everybody else
  64.  
    semacquire(&worldsema)
  65.  
     
  66.  
    // 跟踪处理
  67.  
    if trace.enabled {
  68.  
    traceGCStart()
  69.  
    }
  70.  
     
  71.  
    // 启动后台扫描任务(G)
  72.  
    if mode == gcBackgroundMode {
  73.  
    gcBgMarkStartWorkers()
  74.  
    }
  75.  
     
  76.  
    // 重置标记相关的状态
  77.  
    gcResetMarkState()
  78.  
     
  79.  
    // 重置参数
  80.  
    work.stwprocs, work.maxprocs = gcprocs(), gomaxprocs
  81.  
    work.heap0 = atomic.Load64(&memstats.heap_live)
  82.  
    work.pauseNS = 0
  83.  
    work.mode = mode
  84.  
     
  85.  
    // 记录开始时间
  86.  
    now := nanotime()
  87.  
    work.tSweepTerm = now
  88.  
    work.pauseStart = now
  89.  
     
  90.  
    // 停止所有运行中的G, 并禁止它们运行
  91.  
    systemstack(stopTheWorldWithSema)
  92.  
     
  93.  
    // !!!!!!!!!!!!!!!!
  94.  
    // 世界已停止(STW)...
  95.  
    // !!!!!!!!!!!!!!!!
  96.  
     
  97.  
    // 清扫上一轮GC未清扫的span, 确保上一轮GC已完成
  98.  
    // Finish sweep before we start concurrent scan.
  99.  
    systemstack(func() {
  100.  
    finishsweep_m()
  101.  
    })
  102.  
    // 清扫sched.sudogcache和sched.deferpool
  103.  
    // clearpools before we start the GC. If we wait they memory will not be
  104.  
    // reclaimed until the next GC cycle.
  105.  
    clearpools()
  106.  
     
  107.  
    // 增加GC计数
  108.  
    work.cycles++
  109.  
     
  110.  
    // 判断是否并行GC模式
  111.  
    if mode == gcBackgroundMode { // Do as much work concurrently as possible
  112.  
    // 标记新一轮GC已开始
  113.  
    gcController.startCycle()
  114.  
    work.heapGoal = memstats.next_gc
  115.  
     
  116.  
    // 设置全局变量中的GC状态为_GCmark
  117.  
    // 然后启用写屏障
  118.  
    // Enter concurrent mark phase and enable
  119.  
    // write barriers.
  120.  
    //
  121.  
    // Because the world is stopped, all Ps will
  122.  
    // observe that write barriers are enabled by
  123.  
    // the time we start the world and begin
  124.  
    // scanning.
  125.  
    //
  126.  
    // Write barriers must be enabled before assists are
  127.  
    // enabled because they must be enabled before
  128.  
    // any non-leaf heap objects are marked. Since
  129.  
    // allocations are blocked until assists can
  130.  
    // happen, we want enable assists as early as
  131.  
    // possible.
  132.  
    setGCPhase(_GCmark)
  133.  
     
  134.  
    // 重置后台标记任务的计数
  135.  
    gcBgMarkPrepare() // Must happen before assist enable.
  136.  
     
  137.  
    // 计算扫描根对象的任务数量
  138.  
    gcMarkRootPrepare()
  139.  
     
  140.  
    // 标记所有tiny alloc等待合并的对象
  141.  
    // Mark all active tinyalloc blocks. Since we're
  142.  
    // allocating from these, they need to be black like
  143.  
    // other allocations. The alternative is to blacken
  144.  
    // the tiny block on every allocation from it, which
  145.  
    // would slow down the tiny allocator.
  146.  
    gcMarkTinyAllocs()
  147.  
     
  148.  
    // 启用辅助GC
  149.  
    // At this point all Ps have enabled the write
  150.  
    // barrier, thus maintaining the no white to
  151.  
    // black invariant. Enable mutator assists to
  152.  
    // put back-pressure on fast allocating
  153.  
    // mutators.
  154.  
    atomic.Store(&gcBlackenEnabled, 1)
  155.  
     
  156.  
    // 记录标记开始的时间
  157.  
    // Assists and workers can start the moment we start
  158.  
    // the world.
  159.  
    gcController.markStartTime = now
  160.  
     
  161.  
    // 重新启动世界
  162.  
    // 前面创建的后台标记任务会开始工作, 所有后台标记任务都完成工作后, 进入完成标记阶段
  163.  
    // Concurrent mark.
  164.  
    systemstack(startTheWorldWithSema)
  165.  
     
  166.  
    // !!!!!!!!!!!!!!!
  167.  
    // 世界已重新启动...
  168.  
    // !!!!!!!!!!!!!!!
  169.  
     
  170.  
    // 记录停止了多久, 和标记阶段开始的时间
  171.  
    now = nanotime()
  172.  
    work.pauseNS += now - work.pauseStart
  173.  
    work.tMark = now
  174.  
    } else {
  175.  
    // 不是并行GC模式
  176.  
    // 记录完成标记阶段开始的时间
  177.  
    t := nanotime()
  178.  
    work.tMark, work.tMarkTerm = t, t
  179.  
    work.heapGoal = work.heap0
  180.  
     
  181.  
    // 跳过标记阶段, 执行完成标记阶段
  182.  
    // 所有标记工作都会在世界已停止的状态执行
  183.  
    // (标记阶段会设置work.markrootDone=true, 如果跳过则它的值是false, 完成标记阶段会执行所有工作)
  184.  
    // 完成标记阶段会重新启动世界
  185.  
    // Perform mark termination. This will restart the world.
  186.  
    gcMarkTermination(memstats.triggerRatio)
  187.  
    }
  188.  
     
  189.  
    semrelease(&work.startSema)
  190.  
    }

接下来一个个分析gcStart调用的函数, 建议配合上面的"回收对象的流程"中的图理解.

函数gcBgMarkStartWorkers用于启动后台标记任务, 先分别对每个P启动一个:

  1.  
    // gcBgMarkStartWorkers prepares background mark worker goroutines.
  2.  
    // These goroutines will not run until the mark phase, but they must
  3.  
    // be started while the work is not stopped and from a regular G
  4.  
    // stack. The caller must hold worldsema.
  5.  
    func gcBgMarkStartWorkers() {
  6.  
    // Background marking is performed by per-P G's. Ensure that
  7.  
    // each P has a background GC G.
  8.  
    for _, p := range &allp {
  9.  
    if p == nil || p.status == _Pdead {
  10.  
    break
  11.  
    }
  12.  
    // 如果已启动则不重复启动
  13.  
    if p.gcBgMarkWorker == 0 {
  14.  
    go gcBgMarkWorker(p)
  15.  
    // 启动后等待该任务通知信号量bgMarkReady再继续
  16.  
    notetsleepg(&work.bgMarkReady, -1)
  17.  
    noteclear(&work.bgMarkReady)
  18.  
    }
  19.  
    }
  20.  
    }

这里虽然为每个P启动了一个后台标记任务, 但是可以同时工作的只有25%, 这个逻辑在协程M获取G时调用的findRunnableGCWorker中:

  1.  
    // findRunnableGCWorker returns the background mark worker for _p_ if it
  2.  
    // should be run. This must only be called when gcBlackenEnabled != 0.
  3.  
    func (c *gcControllerState) findRunnableGCWorker(_p_ *p) *g {
  4.  
    if gcBlackenEnabled == 0 {
  5.  
    throw("gcControllerState.findRunnable: blackening not enabled")
  6.  
    }
  7.  
    if _p_.gcBgMarkWorker == 0 {
  8.  
    // The mark worker associated with this P is blocked
  9.  
    // performing a mark transition. We can't run it
  10.  
    // because it may be on some other run or wait queue.
  11.  
    return nil
  12.  
    }
  13.  
     
  14.  
    if !gcMarkWorkAvailable(_p_) {
  15.  
    // No work to be done right now. This can happen at
  16.  
    // the end of the mark phase when there are still
  17.  
    // assists tapering off. Don't bother running a worker
  18.  
    // now because it'll just return immediately.
  19.  
    return nil
  20.  
    }
  21.  
     
  22.  
    // 原子减少对应的值, 如果减少后大于等于0则返回true, 否则返回false
  23.  
    decIfPositive := func(ptr *int64) bool {
  24.  
    if *ptr > 0 {
  25.  
    if atomic.Xaddint64(ptr, -1) >= 0 {
  26.  
    return true
  27.  
    }
  28.  
    // We lost a race
  29.  
    atomic.Xaddint64(ptr, +1)
  30.  
    }
  31.  
    return false
  32.  
    }
  33.  
     
  34.  
    // 减少dedicatedMarkWorkersNeeded, 成功时后台标记任务的模式是Dedicated
  35.  
    // dedicatedMarkWorkersNeeded是当前P的数量的25%去除小数点
  36.  
    // 详见startCycle函数
  37.  
    if decIfPositive(&c.dedicatedMarkWorkersNeeded) {
  38.  
    // This P is now dedicated to marking until the end of
  39.  
    // the concurrent mark phase.
  40.  
    _p_.gcMarkWorkerMode = gcMarkWorkerDedicatedMode
  41.  
    } else {
  42.  
    // 减少fractionalMarkWorkersNeeded, 成功是后台标记任务的模式是Fractional
  43.  
    // 上面的计算如果小数点后有数值(不能够整除)则fractionalMarkWorkersNeeded为1, 否则为0
  44.  
    // 详见startCycle函数
  45.  
    // 举例来说, 4个P时会执行1个Dedicated模式的任务, 5个P时会执行1个Dedicated模式和1个Fractional模式的任务
  46.  
    if !decIfPositive(&c.fractionalMarkWorkersNeeded) {
  47.  
    // No more workers are need right now.
  48.  
    return nil
  49.  
    }
  50.  
     
  51.  
    // 按Dedicated模式的任务的执行时间判断cpu占用率是否超过预算值, 超过时不启动
  52.  
    // This P has picked the token for the fractional worker.
  53.  
    // Is the GC currently under or at the utilization goal?
  54.  
    // If so, do more work.
  55.  
    //
  56.  
    // We used to check whether doing one time slice of work
  57.  
    // would remain under the utilization goal, but that has the
  58.  
    // effect of delaying work until the mutator has run for
  59.  
    // enough time slices to pay for the work. During those time
  60.  
    // slices, write barriers are enabled, so the mutator is running slower.
  61.  
    // Now instead we do the work whenever we're under or at the
  62.  
    // utilization work and pay for it by letting the mutator run later.
  63.  
    // This doesn't change the overall utilization averages, but it
  64.  
    // front loads the GC work so that the GC finishes earlier and
  65.  
    // write barriers can be turned off sooner, effectively giving
  66.  
    // the mutator a faster machine.
  67.  
    //
  68.  
    // The old, slower behavior can be restored by setting
  69.  
    // gcForcePreemptNS = forcePreemptNS.
  70.  
    const gcForcePreemptNS = 0
  71.  
     
  72.  
    // TODO(austin): We could fast path this and basically
  73.  
    // eliminate contention on c.fractionalMarkWorkersNeeded by
  74.  
    // precomputing the minimum time at which it's worth
  75.  
    // next scheduling the fractional worker. Then Ps
  76.  
    // don't have to fight in the window where we've
  77.  
    // passed that deadline and no one has started the
  78.  
    // worker yet.
  79.  
    //
  80.  
    // TODO(austin): Shorter preemption interval for mark
  81.  
    // worker to improve fairness and give this
  82.  
    // finer-grained control over schedule?
  83.  
    now := nanotime() - gcController.markStartTime
  84.  
    then := now + gcForcePreemptNS
  85.  
    timeUsed := c.fractionalMarkTime + gcForcePreemptNS
  86.  
    if then > 0 && float64(timeUsed)/float64(then) > c.fractionalUtilizationGoal {
  87.  
    // Nope, we'd overshoot the utilization goal
  88.  
    atomic.Xaddint64(&c.fractionalMarkWorkersNeeded, +1)
  89.  
    return nil
  90.  
    }
  91.  
    _p_.gcMarkWorkerMode = gcMarkWorkerFractionalMode
  92.  
    }
  93.  
     
  94.  
    // 安排后台标记任务执行
  95.  
    // Run the background mark worker
  96.  
    gp := _p_.gcBgMarkWorker.ptr()
  97.  
    casgstatus(gp, _Gwaiting, _Grunnable)
  98.  
    if trace.enabled {
  99.  
    traceGoUnpark(gp, 0)
  100.  
    }
  101.  
    return gp
  102.  
    }

gcResetMarkState函数会重置标记相关的状态:

  1.  
    // gcResetMarkState resets global state prior to marking (concurrent
  2.  
    // or STW) and resets the stack scan state of all Gs.
  3.  
    //
  4.  
    // This is safe to do without the world stopped because any Gs created
  5.  
    // during or after this will start out in the reset state.
  6.  
    func gcResetMarkState() {
  7.  
    // This may be called during a concurrent phase, so make sure
  8.  
    // allgs doesn't change.
  9.  
    lock(&allglock)
  10.  
    for _, gp := range allgs {
  11.  
    gp.gcscandone = false // set to true in gcphasework
  12.  
    gp.gcscanvalid = false // stack has not been scanned
  13.  
    gp.gcAssistBytes = 0
  14.  
    }
  15.  
    unlock(&allglock)
  16.  
     
  17.  
    work.bytesMarked = 0
  18.  
    work.initialHeapLive = atomic.Load64(&memstats.heap_live)
  19.  
    work.markrootDone = false
  20.  
    }

stopTheWorldWithSema函数会停止整个世界, 这个函数必须在g0中运行:

  1.  
    // stopTheWorldWithSema is the core implementation of stopTheWorld.
  2.  
    // The caller is responsible for acquiring worldsema and disabling
  3.  
    // preemption first and then should stopTheWorldWithSema on the system
  4.  
    // stack:
  5.  
    //
  6.  
    // semacquire(&worldsema, 0)
  7.  
    // m.preemptoff = "reason"
  8.  
    // systemstack(stopTheWorldWithSema)
  9.  
    //
  10.  
    // When finished, the caller must either call startTheWorld or undo
  11.  
    // these three operations separately:
  12.  
    //
  13.  
    // m.preemptoff = ""
  14.  
    // systemstack(startTheWorldWithSema)
  15.  
    // semrelease(&worldsema)
  16.  
    //
  17.  
    // It is allowed to acquire worldsema once and then execute multiple
  18.  
    // startTheWorldWithSema/stopTheWorldWithSema pairs.
  19.  
    // Other P's are able to execute between successive calls to
  20.  
    // startTheWorldWithSema and stopTheWorldWithSema.
  21.  
    // Holding worldsema causes any other goroutines invoking
  22.  
    // stopTheWorld to block.
  23.  
    func stopTheWorldWithSema() {
  24.  
    _g_ := getg()
  25.  
     
  26.  
    // If we hold a lock, then we won't be able to stop another M
  27.  
    // that is blocked trying to acquire the lock.
  28.  
    if _g_.m.locks > 0 {
  29.  
    throw("stopTheWorld: holding locks")
  30.  
    }
  31.  
     
  32.  
    lock(&sched.lock)
  33.  
     
  34.  
    // 需要停止的P数量
  35.  
    sched.stopwait = gomaxprocs
  36.  
     
  37.  
    // 设置gc等待标记, 调度时看见此标记会进入等待
  38.  
    atomic.Store(&sched.gcwaiting, 1)
  39.  
     
  40.  
    // 抢占所有运行中的G
  41.  
    preemptall()
  42.  
     
  43.  
    // 停止当前的P
  44.  
    // stop current P
  45.  
    _g_.m.p.ptr().status = _Pgcstop // Pgcstop is only diagnostic.
  46.  
     
  47.  
    // 减少需要停止的P数量(当前的P算一个)
  48.  
    sched.stopwait--
  49.  
     
  50.  
    // 抢占所有在Psyscall状态的P, 防止它们重新参与调度
  51.  
    // try to retake all P's in Psyscall status
  52.  
    for i := 0; i < int(gomaxprocs); i++ {
  53.  
    p := allp[i]
  54.  
    s := p.status
  55.  
    if s == _Psyscall && atomic.Cas(&p.status, s, _Pgcstop) {
  56.  
    if trace.enabled {
  57.  
    traceGoSysBlock(p)
  58.  
    traceProcStop(p)
  59.  
    }
  60.  
    p.syscalltick++
  61.  
    sched.stopwait--
  62.  
    }
  63.  
    }
  64.  
     
  65.  
    // 防止所有空闲的P重新参与调度
  66.  
    // stop idle P's
  67.  
    for {
  68.  
    p := pidleget()
  69.  
    if p == nil {
  70.  
    break
  71.  
    }
  72.  
    p.status = _Pgcstop
  73.  
    sched.stopwait--
  74.  
    }
  75.  
    wait := sched.stopwait > 0
  76.  
    unlock(&sched.lock)
  77.  
     
  78.  
    // 如果仍有需要停止的P, 则等待它们停止
  79.  
    // wait for remaining P's to stop voluntarily
  80.  
    if wait {
  81.  
    for {
  82.  
    // 循环等待 + 抢占所有运行中的G
  83.  
    // wait for 100us, then try to re-preempt in case of any races
  84.  
    if notetsleep(&sched.stopnote, 100*1000) {
  85.  
    noteclear(&sched.stopnote)
  86.  
    break
  87.  
    }
  88.  
    preemptall()
  89.  
    }
  90.  
    }
  91.  
     
  92.  
    // 逻辑正确性检查
  93.  
    // sanity checks
  94.  
    bad := ""
  95.  
    if sched.stopwait != 0 {
  96.  
    bad = "stopTheWorld: not stopped (stopwait != 0)"
  97.  
    } else {
  98.  
    for i := 0; i < int(gomaxprocs); i++ {
  99.  
    p := allp[i]
  100.  
    if p.status != _Pgcstop {
  101.  
    bad = "stopTheWorld: not stopped (status != _Pgcstop)"
  102.  
    }
  103.  
    }
  104.  
    }
  105.  
    if atomic.Load(&freezing) != 0 {
  106.  
    // Some other thread is panicking. This can cause the
  107.  
    // sanity checks above to fail if the panic happens in
  108.  
    // the signal handler on a stopped thread. Either way,
  109.  
    // we should halt this thread.
  110.  
    lock(&deadlock)
  111.  
    lock(&deadlock)
  112.  
    }
  113.  
    if bad != "" {
  114.  
    throw(bad)
  115.  
    }
  116.  
     
  117.  
    // 到这里所有运行中的G都会变为待运行, 并且所有的P都不能被M获取
  118.  
    // 也就是说所有的go代码(除了当前的)都会停止运行, 并且不能运行新的go代码
  119.  
    }
  120.  
     

finishsweep_m函数会清扫上一轮GC未清扫的span, 确保上一轮GC已完成:

  1.  
    // finishsweep_m ensures that all spans are swept.
  2.  
    //
  3.  
    // The world must be stopped. This ensures there are no sweeps in
  4.  
    // progress.
  5.  
    //
  6.  
    //go:nowritebarrier
  7.  
    func finishsweep_m() {
  8.  
    // sweepone会取出一个未sweep的span然后执行sweep
  9.  
    // 详细将在下面sweep阶段时分析
  10.  
    // Sweeping must be complete before marking commences, so
  11.  
    // sweep any unswept spans. If this is a concurrent GC, there
  12.  
    // shouldn't be any spans left to sweep, so this should finish
  13.  
    // instantly. If GC was forced before the concurrent sweep
  14.  
    // finished, there may be spans to sweep.
  15.  
    for sweepone() != ^uintptr(0) {
  16.  
    sweep.npausesweep++
  17.  
    }
  18.  
     
  19.  
    // 所有span都sweep完成后, 启动一个新的markbit时代
  20.  
    // 这个函数是实现span的gcmarkBits和allocBits的分配和复用的关键, 流程如下
  21.  
    // - span分配gcmarkBits和allocBits
  22.  
    // - span完成sweep
  23.  
    // - 原allocBits不再被使用
  24.  
    // - gcmarkBits变为allocBits
  25.  
    // - 分配新的gcmarkBits
  26.  
    // - 开启新的markbit时代
  27.  
    // - span完成sweep, 同上
  28.  
    // - 开启新的markbit时代
  29.  
    // - 2个时代之前的bitmap将不再被使用, 可以复用这些bitmap
  30.  
    nextMarkBitArenaEpoch()
  31.  
    }

clearpools函数会清理sched.sudogcache和sched.deferpool, 让它们的内存可以被回收:

  1.  
    func clearpools() {
  2.  
    // clear sync.Pools
  3.  
    if poolcleanup != nil {
  4.  
    poolcleanup()
  5.  
    }
  6.  
     
  7.  
    // Clear central sudog cache.
  8.  
    // Leave per-P caches alone, they have strictly bounded size.
  9.  
    // Disconnect cached list before dropping it on the floor,
  10.  
    // so that a dangling ref to one entry does not pin all of them.
  11.  
    lock(&sched.sudoglock)
  12.  
    var sg, sgnext *sudog
  13.  
    for sg = sched.sudogcache; sg != nil; sg = sgnext {
  14.  
    sgnext = sg.next
  15.  
    sg.next = nil
  16.  
    }
  17.  
    sched.sudogcache = nil
  18.  
    unlock(&sched.sudoglock)
  19.  
     
  20.  
    // Clear central defer pools.
  21.  
    // Leave per-P pools alone, they have strictly bounded size.
  22.  
    lock(&sched.deferlock)
  23.  
    for i := range sched.deferpool {
  24.  
    // disconnect cached list before dropping it on the floor,
  25.  
    // so that a dangling ref to one entry does not pin all of them.
  26.  
    var d, dlink *_defer
  27.  
    for d = sched.deferpool[i]; d != nil; d = dlink {
  28.  
    dlink = d.link
  29.  
    d.link = nil
  30.  
    }
  31.  
    sched.deferpool[i] = nil
  32.  
    }
  33.  
    unlock(&sched.deferlock)
  34.  
    }

startCycle标记开始了新一轮的GC:

  1.  
    // startCycle resets the GC controller's state and computes estimates
  2.  
    // for a new GC cycle. The caller must hold worldsema.
  3.  
    func (c *gcControllerState) startCycle() {
  4.  
    c.scanWork = 0
  5.  
    c.bgScanCredit = 0
  6.  
    c.assistTime = 0
  7.  
    c.dedicatedMarkTime = 0
  8.  
    c.fractionalMarkTime = 0
  9.  
    c.idleMarkTime = 0
  10.  
     
  11.  
    // 伪装heap_marked的值如果gc_trigger的值很小, 防止后面对triggerRatio做出错误的调整
  12.  
    // If this is the first GC cycle or we're operating on a very
  13.  
    // small heap, fake heap_marked so it looks like gc_trigger is
  14.  
    // the appropriate growth from heap_marked, even though the
  15.  
    // real heap_marked may not have a meaningful value (on the
  16.  
    // first cycle) or may be much smaller (resulting in a large
  17.  
    // error response).
  18.  
    if memstats.gc_trigger <= heapminimum {
  19.  
    memstats.heap_marked = uint64(float64(memstats.gc_trigger) / (1 + memstats.triggerRatio))
  20.  
    }
  21.  
     
  22.  
    // 重新计算next_gc, 注意next_gc的计算跟gc_trigger不一样
  23.  
    // Re-compute the heap goal for this cycle in case something
  24.  
    // changed. This is the same calculation we use elsewhere.
  25.  
    memstats.next_gc = memstats.heap_marked + memstats.heap_marked*uint64(gcpercent)/100
  26.  
    if gcpercent < 0 {
  27.  
    memstats.next_gc = ^uint64(0)
  28.  
    }
  29.  
     
  30.  
    // 确保next_gc和heap_live之间最少有1MB
  31.  
    // Ensure that the heap goal is at least a little larger than
  32.  
    // the current live heap size. This may not be the case if GC
  33.  
    // start is delayed or if the allocation that pushed heap_live
  34.  
    // over gc_trigger is large or if the trigger is really close to
  35.  
    // GOGC. Assist is proportional to this distance, so enforce a
  36.  
    // minimum distance, even if it means going over the GOGC goal
  37.  
    // by a tiny bit.
  38.  
    if memstats.next_gc < memstats.heap_live+1024*1024 {
  39.  
    memstats.next_gc = memstats.heap_live + 1024*1024
  40.  
    }
  41.  
     
  42.  
    // 计算可以同时执行的后台标记任务的数量
  43.  
    // dedicatedMarkWorkersNeeded等于P的数量的25%去除小数点
  44.  
    // 如果可以整除则fractionalMarkWorkersNeeded等于0否则等于1
  45.  
    // totalUtilizationGoal是GC所占的P的目标值(例如P一共有5个时目标是1.25个P)
  46.  
    // fractionalUtilizationGoal是Fractiona模式的任务所占的P的目标值(例如P一共有5个时目标是0.25个P)
  47.  
    // Compute the total mark utilization goal and divide it among
  48.  
    // dedicated and fractional workers.
  49.  
    totalUtilizationGoal := float64(gomaxprocs) * gcGoalUtilization
  50.  
    c.dedicatedMarkWorkersNeeded = int64(totalUtilizationGoal)
  51.  
    c.fractionalUtilizationGoal = totalUtilizationGoal - float64(c.dedicatedMarkWorkersNeeded)
  52.  
    if c.fractionalUtilizationGoal > 0 {
  53.  
    c.fractionalMarkWorkersNeeded = 1
  54.  
    } else {
  55.  
    c.fractionalMarkWorkersNeeded = 0
  56.  
    }
  57.  
     
  58.  
    // 重置P中的辅助GC所用的时间统计
  59.  
    // Clear per-P state
  60.  
    for _, p := range &allp {
  61.  
    if p == nil {
  62.  
    break
  63.  
    }
  64.  
    p.gcAssistTime = 0
  65.  
    }
  66.  
     
  67.  
    // 计算辅助GC的参数
  68.  
    // 参考上面对计算assistWorkPerByte的公式的分析
  69.  
    // Compute initial values for controls that are updated
  70.  
    // throughout the cycle.
  71.  
    c.revise()
  72.  
     
  73.  
    if debug.gcpacertrace > 0 {
  74.  
    print("pacer: assist ratio=", c.assistWorkPerByte,
  75.  
    " (scan ", memstats.heap_scan>>20, " MB in ",
  76.  
    work.initialHeapLive>>20, "->",
  77.  
    memstats.next_gc>>20, " MB)",
  78.  
    " workers=", c.dedicatedMarkWorkersNeeded,
  79.  
    "+", c.fractionalMarkWorkersNeeded, "\n")
  80.  
    }
  81.  
    }

setGCPhase函数会修改表示当前GC阶段的全局变量和是否开启写屏障的全局变量:

  1.  
    //go:nosplit
  2.  
    func setGCPhase(x uint32) {
  3.  
    atomic.Store(&gcphase, x)
  4.  
    writeBarrier.needed = gcphase == _GCmark || gcphase == _GCmarktermination
  5.  
    writeBarrier.enabled = writeBarrier.needed || writeBarrier.cgo
  6.  
    }

gcBgMarkPrepare函数会重置后台标记任务的计数:

  1.  
    // gcBgMarkPrepare sets up state for background marking.
  2.  
    // Mutator assists must not yet be enabled.
  3.  
    func gcBgMarkPrepare() {
  4.  
    // Background marking will stop when the work queues are empty
  5.  
    // and there are no more workers (note that, since this is
  6.  
    // concurrent, this may be a transient state, but mark
  7.  
    // termination will clean it up). Between background workers
  8.  
    // and assists, we don't really know how many workers there
  9.  
    // will be, so we pretend to have an arbitrarily large number
  10.  
    // of workers, almost all of which are "waiting". While a
  11.  
    // worker is working it decrements nwait. If nproc == nwait,
  12.  
    // there are no workers.
  13.  
    work.nproc = ^uint32(0)
  14.  
    work.nwait = ^uint32(0)
  15.  
    }

gcMarkRootPrepare函数会计算扫描根对象的任务数量:

  1.  
    // gcMarkRootPrepare queues root scanning jobs (stacks, globals, and
  2.  
    // some miscellany) and initializes scanning-related state.
  3.  
    //
  4.  
    // The caller must have call gcCopySpans().
  5.  
    //
  6.  
    // The world must be stopped.
  7.  
    //
  8.  
    //go:nowritebarrier
  9.  
    func gcMarkRootPrepare() {
  10.  
    // 释放mcache中的所有span的任务, 只在完成标记阶段(mark termination)中执行
  11.  
    if gcphase == _GCmarktermination {
  12.  
    work.nFlushCacheRoots = int(gomaxprocs)
  13.  
    } else {
  14.  
    work.nFlushCacheRoots = 0
  15.  
    }
  16.  
     
  17.  
    // 计算block数量的函数, rootBlockBytes是256KB
  18.  
    // Compute how many data and BSS root blocks there are.
  19.  
    nBlocks := func(bytes uintptr) int {
  20.  
    return int((bytes + rootBlockBytes - 1) / rootBlockBytes)
  21.  
    }
  22.  
     
  23.  
    work.nDataRoots = 0
  24.  
    work.nBSSRoots = 0
  25.  
     
  26.  
    // data和bss每一轮GC只扫描一次
  27.  
    // 并行GC中会在后台标记任务中扫描, 完成标记阶段(mark termination)中不扫描
  28.  
    // 非并行GC会在完成标记阶段(mark termination)中扫描
  29.  
    // Only scan globals once per cycle; preferably concurrently.
  30.  
    if !work.markrootDone {
  31.  
    // 计算扫描可读写的全局变量的任务数量
  32.  
    for _, datap := range activeModules() {
  33.  
    nDataRoots := nBlocks(datap.edata - datap.data)
  34.  
    if nDataRoots > work.nDataRoots {
  35.  
    work.nDataRoots = nDataRoots
  36.  
    }
  37.  
    }
  38.  
     
  39.  
    // 计算扫描只读的全局变量的任务数量
  40.  
    for _, datap := range activeModules() {
  41.  
    nBSSRoots := nBlocks(datap.ebss - datap.bss)
  42.  
    if nBSSRoots > work.nBSSRoots {
  43.  
    work.nBSSRoots = nBSSRoots
  44.  
    }
  45.  
    }
  46.  
    }
  47.  
     
  48.  
    // span中的finalizer和各个G的栈每一轮GC只扫描一次
  49.  
    // 同上
  50.  
    if !work.markrootDone {
  51.  
    // 计算扫描span中的finalizer的任务数量
  52.  
    // On the first markroot, we need to scan span roots.
  53.  
    // In concurrent GC, this happens during concurrent
  54.  
    // mark and we depend on addfinalizer to ensure the
  55.  
    // above invariants for objects that get finalizers
  56.  
    // after concurrent mark. In STW GC, this will happen
  57.  
    // during mark termination.
  58.  
    //
  59.  
    // We're only interested in scanning the in-use spans,
  60.  
    // which will all be swept at this point. More spans
  61.  
    // may be added to this list during concurrent GC, but
  62.  
    // we only care about spans that were allocated before
  63.  
    // this mark phase.
  64.  
    work.nSpanRoots = mheap_.sweepSpans[mheap_.sweepgen/2%2].numBlocks()
  65.  
     
  66.  
    // 计算扫描各个G的栈的任务数量
  67.  
    // On the first markroot, we need to scan all Gs. Gs
  68.  
    // may be created after this point, but it's okay that
  69.  
    // we ignore them because they begin life without any
  70.  
    // roots, so there's nothing to scan, and any roots
  71.  
    // they create during the concurrent phase will be
  72.  
    // scanned during mark termination. During mark
  73.  
    // termination, allglen isn't changing, so we'll scan
  74.  
    // all Gs.
  75.  
    work.nStackRoots = int(atomic.Loaduintptr(&allglen))
  76.  
    } else {
  77.  
    // We've already scanned span roots and kept the scan
  78.  
    // up-to-date during concurrent mark.
  79.  
    work.nSpanRoots = 0
  80.  
     
  81.  
    // The hybrid barrier ensures that stacks can't
  82.  
    // contain pointers to unmarked objects, so on the
  83.  
    // second markroot, there's no need to scan stacks.
  84.  
    work.nStackRoots = 0
  85.  
     
  86.  
    if debug.gcrescanstacks > 0 {
  87.  
    // Scan stacks anyway for debugging.
  88.  
    work.nStackRoots = int(atomic.Loaduintptr(&allglen))
  89.  
    }
  90.  
    }
  91.  
     
  92.  
    // 计算总任务数量
  93.  
    // 后台标记任务会对markrootNext进行原子递增, 来决定做哪个任务
  94.  
    // 这种用数值来实现锁自由队列的办法挺聪明的, 尽管google工程师觉得不好(看后面markroot函数的分析)
  95.  
    work.markrootNext = 0
  96.  
    work.markrootJobs = uint32(fixedRootCount + work.nFlushCacheRoots + work.nDataRoots + work.nBSSRoots + work.nSpanRoots + work.nStackRoots)
  97.  
    }

gcMarkTinyAllocs函数会标记所有tiny alloc等待合并的对象:

  1.  
    // gcMarkTinyAllocs greys all active tiny alloc blocks.
  2.  
    //
  3.  
    // The world must be stopped.
  4.  
    func gcMarkTinyAllocs() {
  5.  
    for _, p := range &allp {
  6.  
    if p == nil || p.status == _Pdead {
  7.  
    break
  8.  
    }
  9.  
    c := p.mcache
  10.  
    if c == nil || c.tiny == 0 {
  11.  
    continue
  12.  
    }
  13.  
    // 标记各个P中的mcache中的tiny
  14.  
    // 在上面的mallocgc函数中可以看到tiny是当前等待合并的对象
  15.  
    _, hbits, span, objIndex := heapBitsForObject(c.tiny, 0, 0)
  16.  
    gcw := &p.gcw
  17.  
    // 标记一个对象存活, 并把它加到标记队列(该对象变为灰色)
  18.  
    greyobject(c.tiny, 0, 0, hbits, span, gcw, objIndex)
  19.  
    // gcBlackenPromptly变量表示当前是否禁止本地队列, 如果已禁止则把标记任务flush到全局队列
  20.  
    if gcBlackenPromptly {
  21.  
    gcw.dispose()
  22.  
    }
  23.  
    }
  24.  
    }

startTheWorldWithSema函数会重新启动世界:

  1.  
    func startTheWorldWithSema() {
  2.  
    _g_ := getg()
  3.  
     
  4.  
    // 禁止G被抢占
  5.  
    _g_.m.locks++ // disable preemption because it can be holding p in a local var
  6.  
     
  7.  
    // 判断收到的网络事件(fd可读可写或错误)并添加对应的G到待运行队列
  8.  
    gp := netpoll(false) // non-blocking
  9.  
    injectglist(gp)
  10.  
     
  11.  
    // 判断是否要启动gc helper
  12.  
    add := needaddgcproc()
  13.  
    lock(&sched.lock)
  14.  
     
  15.  
    // 如果要求改变gomaxprocs则调整P的数量
  16.  
    // procresize会返回有可运行任务的P的链表
  17.  
    procs := gomaxprocs
  18.  
    if newprocs != 0 {
  19.  
    procs = newprocs
  20.  
    newprocs = 0
  21.  
    }
  22.  
    p1 := procresize(procs)
  23.  
     
  24.  
    // 取消GC等待标记
  25.  
    sched.gcwaiting = 0
  26.  
     
  27.  
    // 如果sysmon在等待则唤醒它
  28.  
    if sched.sysmonwait != 0 {
  29.  
    sched.sysmonwait = 0
  30.  
    notewakeup(&sched.sysmonnote)
  31.  
    }
  32.  
    unlock(&sched.lock)
  33.  
     
  34.  
    // 唤醒有可运行任务的P
  35.  
    for p1 != nil {
  36.  
    p := p1
  37.  
    p1 = p1.link.ptr()
  38.  
    if p.m != 0 {
  39.  
    mp := p.m.ptr()
  40.  
    p.m = 0
  41.  
    if mp.nextp != 0 {
  42.  
    throw("startTheWorld: inconsistent mp->nextp")
  43.  
    }
  44.  
    mp.nextp.set(p)
  45.  
    notewakeup(&mp.park)
  46.  
    } else {
  47.  
    // Start M to run P. Do not start another M below.
  48.  
    newm(nil, p)
  49.  
    add = false
  50.  
    }
  51.  
    }
  52.  
     
  53.  
    // 如果有空闲的P,并且没有自旋中的M则唤醒或者创建一个M
  54.  
    // Wakeup an additional proc in case we have excessive runnable goroutines
  55.  
    // in local queues or in the global queue. If we don't, the proc will park itself.
  56.  
    // If we have lots of excessive work, resetspinning will unpark additional procs as necessary.
  57.  
    if atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 {
  58.  
    wakep()
  59.  
    }
  60.  
     
  61.  
    // 启动gc helper
  62.  
    if add {
  63.  
    // If GC could have used another helper proc, start one now,
  64.  
    // in the hope that it will be available next time.
  65.  
    // It would have been even better to start it before the collection,
  66.  
    // but doing so requires allocating memory, so it's tricky to
  67.  
    // coordinate. This lazy approach works out in practice:
  68.  
    // we don't mind if the first couple gc rounds don't have quite
  69.  
    // the maximum number of procs.
  70.  
    newm(mhelpgc, nil)
  71.  
    }
  72.  
     
  73.  
    // 允许G被抢占
  74.  
    _g_.m.locks--
  75.  
     
  76.  
    // 如果当前G要求被抢占则重新尝试
  77.  
    if _g_.m.locks == 0 && _g_.preempt { // restore the preemption request in case we've cleared it in newstack
  78.  
    _g_.stackguard0 = stackPreempt
  79.  
    }
  80.  
    }
  81.  
     

重启世界后各个M会重新开始调度, 调度时会优先使用上面提到的findRunnableGCWorker函数查找任务, 之后就有大约25%的P运行后台标记任务.
后台标记任务的函数是gcBgMarkWorker:

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

gcDrain函数用于执行标记:

  1.  
    // gcDrain scans roots and objects in work buffers, blackening grey
  2.  
    // objects until all roots and work buffers have been drained.
  3.  
    //
  4.  
    // If flags&gcDrainUntilPreempt != 0, gcDrain returns when g.preempt
  5.  
    // is set. This implies gcDrainNoBlock.
  6.  
    //
  7.  
    // If flags&gcDrainIdle != 0, gcDrain returns when there is other work
  8.  
    // to do. This implies gcDrainNoBlock.
  9.  
    //
  10.  
    // If flags&gcDrainNoBlock != 0, gcDrain returns as soon as it is
  11.  
    // unable to get more work. Otherwise, it will block until all
  12.  
    // blocking calls are blocked in gcDrain.
  13.  
    //
  14.  
    // If flags&gcDrainFlushBgCredit != 0, gcDrain flushes scan work
  15.  
    // credit to gcController.bgScanCredit every gcCreditSlack units of
  16.  
    // scan work.
  17.  
    //
  18.  
    //go:nowritebarrier
  19.  
    func gcDrain(gcw *gcWork, flags gcDrainFlags) {
  20.  
    if !writeBarrier.needed {
  21.  
    throw("gcDrain phase incorrect")
  22.  
    }
  23.  
     
  24.  
    gp := getg().m.curg
  25.  
     
  26.  
    // 看到抢占标志时是否要返回
  27.  
    preemptible := flags&gcDrainUntilPreempt != 0
  28.  
     
  29.  
    // 没有任务时是否要等待任务
  30.  
    blocking := flags&(gcDrainUntilPreempt|gcDrainIdle|gcDrainNoBlock) == 0
  31.  
     
  32.  
    // 是否计算后台的扫描量来减少辅助GC和唤醒等待中的G
  33.  
    flushBgCredit := flags&gcDrainFlushBgCredit != 0
  34.  
     
  35.  
    // 是否只执行一定量的工作
  36.  
    idle := flags&gcDrainIdle != 0
  37.  
     
  38.  
    // 记录初始的已扫描数量
  39.  
    initScanWork := gcw.scanWork
  40.  
     
  41.  
    // 扫描idleCheckThreshold(100000)个对象以后检查是否要返回
  42.  
    // idleCheck is the scan work at which to perform the next
  43.  
    // idle check with the scheduler.
  44.  
    idleCheck := initScanWork + idleCheckThreshold
  45.  
     
  46.  
    // 如果根对象未扫描完, 则先扫描根对象
  47.  
    // Drain root marking jobs.
  48.  
    if work.markrootNext < work.markrootJobs {
  49.  
    // 如果标记了preemptible, 循环直到被抢占
  50.  
    for !(preemptible && gp.preempt) {
  51.  
    // 从根对象扫描队列取出一个值(原子递增)
  52.  
    job := atomic.Xadd(&work.markrootNext, +1) - 1
  53.  
    if job >= work.markrootJobs {
  54.  
    break
  55.  
    }
  56.  
    // 执行根对象扫描工作
  57.  
    markroot(gcw, job)
  58.  
    // 如果是idle模式并且有其他工作, 则返回
  59.  
    if idle && pollWork() {
  60.  
    goto done
  61.  
    }
  62.  
    }
  63.  
    }
  64.  
     
  65.  
    // 根对象已经在标记队列中, 消费标记队列
  66.  
    // 如果标记了preemptible, 循环直到被抢占
  67.  
    // Drain heap marking jobs.
  68.  
    for !(preemptible && gp.preempt) {
  69.  
    // 如果全局标记队列为空, 把本地标记队列的一部分工作分过去
  70.  
    // (如果wbuf2不为空则移动wbuf2过去, 否则移动wbuf1的一半过去)
  71.  
    // Try to keep work available on the global queue. We used to
  72.  
    // check if there were waiting workers, but it's better to
  73.  
    // just keep work available than to make workers wait. In the
  74.  
    // worst case, we'll do O(log(_WorkbufSize)) unnecessary
  75.  
    // balances.
  76.  
    if work.full == 0 {
  77.  
    gcw.balance()
  78.  
    }
  79.  
     
  80.  
    // 从本地标记队列中获取对象, 获取不到则从全局标记队列获取
  81.  
    var b uintptr
  82.  
    if blocking {
  83.  
    // 阻塞获取
  84.  
    b = gcw.get()
  85.  
    } else {
  86.  
    // 非阻塞获取
  87.  
    b = gcw.tryGetFast()
  88.  
    if b == 0 {
  89.  
    b = gcw.tryGet()
  90.  
    }
  91.  
    }
  92.  
     
  93.  
    // 获取不到对象, 标记队列已为空, 跳出循环
  94.  
    if b == 0 {
  95.  
    // work barrier reached or tryGet failed.
  96.  
    break
  97.  
    }
  98.  
     
  99.  
    // 扫描获取到的对象
  100.  
    scanobject(b, gcw)
  101.  
     
  102.  
    // 如果已经扫描了一定数量的对象(gcCreditSlack的值是2000)
  103.  
    // Flush background scan work credit to the global
  104.  
    // account if we've accumulated enough locally so
  105.  
    // mutator assists can draw on it.
  106.  
    if gcw.scanWork >= gcCreditSlack {
  107.  
    // 把扫描的对象数量添加到全局
  108.  
    atomic.Xaddint64(&gcController.scanWork, gcw.scanWork)
  109.  
    // 减少辅助GC的工作量和唤醒等待中的G
  110.  
    if flushBgCredit {
  111.  
    gcFlushBgCredit(gcw.scanWork - initScanWork)
  112.  
    initScanWork = 0
  113.  
    }
  114.  
    idleCheck -= gcw.scanWork
  115.  
    gcw.scanWork = 0
  116.  
     
  117.  
    // 如果是idle模式且达到了检查的扫描量, 则检查是否有其他任务(G), 如果有则跳出循环
  118.  
    if idle && idleCheck <= 0 {
  119.  
    idleCheck += idleCheckThreshold
  120.  
    if pollWork() {
  121.  
    break
  122.  
    }
  123.  
    }
  124.  
    }
  125.  
    }
  126.  
     
  127.  
    // In blocking mode, write barriers are not allowed after this
  128.  
    // point because we must preserve the condition that the work
  129.  
    // buffers are empty.
  130.  
     
  131.  
    done:
  132.  
    // 把扫描的对象数量添加到全局
  133.  
    // Flush remaining scan work credit.
  134.  
    if gcw.scanWork > 0 {
  135.  
    atomic.Xaddint64(&gcController.scanWork, gcw.scanWork)
  136.  
    // 减少辅助GC的工作量和唤醒等待中的G
  137.  
    if flushBgCredit {
  138.  
    gcFlushBgCredit(gcw.scanWork - initScanWork)
  139.  
    }
  140.  
    gcw.scanWork = 0
  141.  
    }
  142.  
    }
  143.  
     

markroot函数用于执行根对象扫描工作:

  1.  
    // markroot scans the i'th root.
  2.  
    //
  3.  
    // Preemption must be disabled (because this uses a gcWork).
  4.  
    //
  5.  
    // nowritebarrier is only advisory here.
  6.  
    //
  7.  
    //go:nowritebarrier
  8.  
    func markroot(gcw *gcWork, i uint32) {
  9.  
    // 判断取出的数值对应哪种任务
  10.  
    // (google的工程师觉得这种办法可笑)
  11.  
    // TODO(austin): This is a bit ridiculous. Compute and store
  12.  
    // the bases in gcMarkRootPrepare instead of the counts.
  13.  
    baseFlushCache := uint32(fixedRootCount)
  14.  
    baseData := baseFlushCache + uint32(work.nFlushCacheRoots)
  15.  
    baseBSS := baseData + uint32(work.nDataRoots)
  16.  
    baseSpans := baseBSS + uint32(work.nBSSRoots)
  17.  
    baseStacks := baseSpans + uint32(work.nSpanRoots)
  18.  
    end := baseStacks + uint32(work.nStackRoots)
  19.  
     
  20.  
    // Note: if you add a case here, please also update heapdump.go:dumproots.
  21.  
    switch {
  22.  
    // 释放mcache中的所有span, 要求STW
  23.  
    case baseFlushCache <= i && i < baseData:
  24.  
    flushmcache(int(i - baseFlushCache))
  25.  
     
  26.  
    // 扫描可读写的全局变量
  27.  
    // 这里只会扫描i对应的block, 扫描时传入包含哪里有指针的bitmap数据
  28.  
    case baseData <= i && i < baseBSS:
  29.  
    for _, datap := range activeModules() {
  30.  
    markrootBlock(datap.data, datap.edata-datap.data, datap.gcdatamask.bytedata, gcw, int(i-baseData))
  31.  
    }
  32.  
     
  33.  
    // 扫描只读的全局变量
  34.  
    // 这里只会扫描i对应的block, 扫描时传入包含哪里有指针的bitmap数据
  35.  
    case baseBSS <= i && i < baseSpans:
  36.  
    for _, datap := range activeModules() {
  37.  
    markrootBlock(datap.bss, datap.ebss-datap.bss, datap.gcbssmask.bytedata, gcw, int(i-baseBSS))
  38.  
    }
  39.  
     
  40.  
    // 扫描析构器队列
  41.  
    case i == fixedRootFinalizers:
  42.  
    // Only do this once per GC cycle since we don't call
  43.  
    // queuefinalizer during marking.
  44.  
    if work.markrootDone {
  45.  
    break
  46.  
    }
  47.  
    for fb := allfin; fb != nil; fb = fb.alllink {
  48.  
    cnt := uintptr(atomic.Load(&fb.cnt))
  49.  
    scanblock(uintptr(unsafe.Pointer(&fb.fin[0])), cnt*unsafe.Sizeof(fb.fin[0]), &finptrmask[0], gcw)
  50.  
    }
  51.  
     
  52.  
    // 释放已中止的G的栈
  53.  
    case i == fixedRootFreeGStacks:
  54.  
    // Only do this once per GC cycle; preferably
  55.  
    // concurrently.
  56.  
    if !work.markrootDone {
  57.  
    // Switch to the system stack so we can call
  58.  
    // stackfree.
  59.  
    systemstack(markrootFreeGStacks)
  60.  
    }
  61.  
     
  62.  
    // 扫描各个span中特殊对象(析构器列表)
  63.  
    case baseSpans <= i && i < baseStacks:
  64.  
    // mark MSpan.specials
  65.  
    markrootSpans(gcw, int(i-baseSpans))
  66.  
     
  67.  
    // 扫描各个G的栈
  68.  
    default:
  69.  
    // 获取需要扫描的G
  70.  
    // the rest is scanning goroutine stacks
  71.  
    var gp *g
  72.  
    if baseStacks <= i && i < end {
  73.  
    gp = allgs[i-baseStacks]
  74.  
    } else {
  75.  
    throw("markroot: bad index")
  76.  
    }
  77.  
     
  78.  
    // 记录等待开始的时间
  79.  
    // remember when we've first observed the G blocked
  80.  
    // needed only to output in traceback
  81.  
    status := readgstatus(gp) // We are not in a scan state
  82.  
    if (status == _Gwaiting || status == _Gsyscall) && gp.waitsince == 0 {
  83.  
    gp.waitsince = work.tstart
  84.  
    }
  85.  
     
  86.  
    // 切换到g0运行(有可能会扫到自己的栈)
  87.  
    // scang must be done on the system stack in case
  88.  
    // we're trying to scan our own stack.
  89.  
    systemstack(func() {
  90.  
    // 判断扫描的栈是否自己的
  91.  
    // If this is a self-scan, put the user G in
  92.  
    // _Gwaiting to prevent self-deadlock. It may
  93.  
    // already be in _Gwaiting if this is a mark
  94.  
    // worker or we're in mark termination.
  95.  
    userG := getg().m.curg
  96.  
    selfScan := gp == userG && readgstatus(userG) == _Grunning
  97.  
     
  98.  
    // 如果正在扫描自己的栈则切换状态到等待中防止死锁
  99.  
    if selfScan {
  100.  
    casgstatus(userG, _Grunning, _Gwaiting)
  101.  
    userG.waitreason = "garbage collection scan"
  102.  
    }
  103.  
     
  104.  
    // 扫描G的栈
  105.  
    // TODO: scang blocks until gp's stack has
  106.  
    // been scanned, which may take a while for
  107.  
    // running goroutines. Consider doing this in
  108.  
    // two phases where the first is non-blocking:
  109.  
    // we scan the stacks we can and ask running
  110.  
    // goroutines to scan themselves; and the
  111.  
    // second blocks.
  112.  
    scang(gp, gcw)
  113.  
     
  114.  
    // 如果正在扫描自己的栈则把状态切换回运行中
  115.  
    if selfScan {
  116.  
    casgstatus(userG, _Gwaiting, _Grunning)
  117.  
    }
  118.  
    })
  119.  
    }
  120.  
    }

scang函数负责扫描G的栈:

  1.  
    // scang blocks until gp's stack has been scanned.
  2.  
    // It might be scanned by scang or it might be scanned by the goroutine itself.
  3.  
    // Either way, the stack scan has completed when scang returns.
  4.  
    func scang(gp *g, gcw *gcWork) {
  5.  
    // Invariant; we (the caller, markroot for a specific goroutine) own gp.gcscandone.
  6.  
    // Nothing is racing with us now, but gcscandone might be set to true left over
  7.  
    // from an earlier round of stack scanning (we scan twice per GC).
  8.  
    // We use gcscandone to record whether the scan has been done during this round.
  9.  
     
  10.  
    // 标记扫描未完成
  11.  
    gp.gcscandone = false
  12.  
     
  13.  
    // See http://golang.org/cl/21503 for justification of the yield delay.
  14.  
    const yieldDelay = 10 * 1000
  15.  
    var nextYield int64
  16.  
     
  17.  
    // 循环直到扫描完成
  18.  
    // Endeavor to get gcscandone set to true,
  19.  
    // either by doing the stack scan ourselves or by coercing gp to scan itself.
  20.  
    // gp.gcscandone can transition from false to true when we're not looking
  21.  
    // (if we asked for preemption), so any time we lock the status using
  22.  
    // castogscanstatus we have to double-check that the scan is still not done.
  23.  
    loop:
  24.  
    for i := 0; !gp.gcscandone; i++ {
  25.  
    // 判断G的当前状态
  26.  
    switch s := readgstatus(gp); s {
  27.  
    default:
  28.  
    dumpgstatus(gp)
  29.  
    throw("stopg: invalid status")
  30.  
     
  31.  
    // G已中止, 不需要扫描它
  32.  
    case _Gdead:
  33.  
    // No stack.
  34.  
    gp.gcscandone = true
  35.  
    break loop
  36.  
     
  37.  
    // G的栈正在扩展, 下一轮重试
  38.  
    case _Gcopystack:
  39.  
    // Stack being switched. Go around again.
  40.  
     
  41.  
    // G不是运行中, 首先需要防止它运行
  42.  
    case _Grunnable, _Gsyscall, _Gwaiting:
  43.  
    // Claim goroutine by setting scan bit.
  44.  
    // Racing with execution or readying of gp.
  45.  
    // The scan bit keeps them from running
  46.  
    // the goroutine until we're done.
  47.  
    if castogscanstatus(gp, s, s|_Gscan) {
  48.  
    // 原子切换状态成功时扫描它的栈
  49.  
    if !gp.gcscandone {
  50.  
    scanstack(gp, gcw)
  51.  
    gp.gcscandone = true
  52.  
    }
  53.  
    // 恢复G的状态, 并跳出循环
  54.  
    restartg(gp)
  55.  
    break loop
  56.  
    }
  57.  
     
  58.  
    // G正在扫描它自己, 等待扫描完毕
  59.  
    case _Gscanwaiting:
  60.  
    // newstack is doing a scan for us right now. Wait.
  61.  
     
  62.  
    // G正在运行
  63.  
    case _Grunning:
  64.  
    // Goroutine running. Try to preempt execution so it can scan itself.
  65.  
    // The preemption handler (in newstack) does the actual scan.
  66.  
     
  67.  
    // 如果已经有抢占请求, 则抢占成功时会帮我们处理
  68.  
    // Optimization: if there is already a pending preemption request
  69.  
    // (from the previous loop iteration), don't bother with the atomics.
  70.  
    if gp.preemptscan && gp.preempt && gp.stackguard0 == stackPreempt {
  71.  
    break
  72.  
    }
  73.  
     
  74.  
    // 抢占G, 抢占成功时G会扫描它自己
  75.  
    // Ask for preemption and self scan.
  76.  
    if castogscanstatus(gp, _Grunning, _Gscanrunning) {
  77.  
    if !gp.gcscandone {
  78.  
    gp.preemptscan = true
  79.  
    gp.preempt = true
  80.  
    gp.stackguard0 = stackPreempt
  81.  
    }
  82.  
    casfrom_Gscanstatus(gp, _Gscanrunning, _Grunning)
  83.  
    }
  84.  
    }
  85.  
     
  86.  
    // 第一轮休眠10毫秒, 第二轮休眠5毫秒
  87.  
    if i == 0 {
  88.  
    nextYield = nanotime() + yieldDelay
  89.  
    }
  90.  
    if nanotime() < nextYield {
  91.  
    procyield(10)
  92.  
    } else {
  93.  
    osyield()
  94.  
    nextYield = nanotime() + yieldDelay/2
  95.  
    }
  96.  
    }
  97.  
     
  98.  
    // 扫描完成, 取消抢占扫描的请求
  99.  
    gp.preemptscan = false // cancel scan request if no longer needed
  100.  
    }

设置preemptscan后, 在抢占G成功时会调用scanstack扫描它自己的栈, 具体代码在这里.
扫描栈用的函数是scanstack:

  1.  
    // scanstack scans gp's stack, greying all pointers found on the stack.
  2.  
    //
  3.  
    // scanstack is marked go:systemstack because it must not be preempted
  4.  
    // while using a workbuf.
  5.  
    //
  6.  
    //go:nowritebarrier
  7.  
    //go:systemstack
  8.  
    func scanstack(gp *g, gcw *gcWork) {
  9.  
    if gp.gcscanvalid {
  10.  
    return
  11.  
    }
  12.  
     
  13.  
    if readgstatus(gp)&_Gscan == 0 {
  14.  
    print("runtime:scanstack: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", hex(readgstatus(gp)), "\n")
  15.  
    throw("scanstack - bad status")
  16.  
    }
  17.  
     
  18.  
    switch readgstatus(gp) &^ _Gscan {
  19.  
    default:
  20.  
    print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")
  21.  
    throw("mark - bad status")
  22.  
    case _Gdead:
  23.  
    return
  24.  
    case _Grunning:
  25.  
    print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")
  26.  
    throw("scanstack: goroutine not stopped")
  27.  
    case _Grunnable, _Gsyscall, _Gwaiting:
  28.  
    // ok
  29.  
    }
  30.  
     
  31.  
    if gp == getg() {
  32.  
    throw("can't scan our own stack")
  33.  
    }
  34.  
    mp := gp.m
  35.  
    if mp != nil && mp.helpgc != 0 {
  36.  
    throw("can't scan gchelper stack")
  37.  
    }
  38.  
     
  39.  
    // Shrink the stack if not much of it is being used. During
  40.  
    // concurrent GC, we can do this during concurrent mark.
  41.  
    if !work.markrootDone {
  42.  
    shrinkstack(gp)
  43.  
    }
  44.  
     
  45.  
    // Scan the stack.
  46.  
    var cache pcvalueCache
  47.  
    scanframe := func(frame *stkframe, unused unsafe.Pointer) bool {
  48.  
    // scanframeworker会根据代码地址(pc)获取函数信息
  49.  
    // 然后找到函数信息中的stackmap.bytedata, 它保存了函数的栈上哪些地方有指针
  50.  
    // 再调用scanblock来扫描函数的栈空间, 同时函数的参数也会这样扫描
  51.  
    scanframeworker(frame, &cache, gcw)
  52.  
    return true
  53.  
    }
  54.  
    // 枚举所有调用帧, 分别调用scanframe函数
  55.  
    gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, scanframe, nil, 0)
  56.  
    // 枚举所有defer的调用帧, 分别调用scanframe函数
  57.  
    tracebackdefers(gp, scanframe, nil)
  58.  
    gp.gcscanvalid = true
  59.  
    }

scanblock函数是一个通用的扫描函数, 扫描全局变量和栈空间都会用它, 和scanobject不同的是bitmap需要手动传入:

  1.  
    // scanblock scans b as scanobject would, but using an explicit
  2.  
    // pointer bitmap instead of the heap bitmap.
  3.  
    //
  4.  
    // This is used to scan non-heap roots, so it does not update
  5.  
    // gcw.bytesMarked or gcw.scanWork.
  6.  
    //
  7.  
    //go:nowritebarrier
  8.  
    func scanblock(b0, n0 uintptr, ptrmask *uint8, gcw *gcWork) {
  9.  
    // Use local copies of original parameters, so that a stack trace
  10.  
    // due to one of the throws below shows the original block
  11.  
    // base and extent.
  12.  
    b := b0
  13.  
    n := n0
  14.  
     
  15.  
    arena_start := mheap_.arena_start
  16.  
    arena_used := mheap_.arena_used
  17.  
     
  18.  
    // 枚举扫描的地址
  19.  
    for i := uintptr(0); i < n; {
  20.  
    // 找到bitmap中对应的byte
  21.  
    // Find bits for the next word.
  22.  
    bits := uint32(*addb(ptrmask, i/(sys.PtrSize*8)))
  23.  
    if bits == 0 {
  24.  
    i += sys.PtrSize * 8
  25.  
    continue
  26.  
    }
  27.  
    // 枚举byte
  28.  
    for j := 0; j < 8 && i < n; j++ {
  29.  
    // 如果该地址包含指针
  30.  
    if bits&1 != 0 {
  31.  
    // 标记在该地址的对象存活, 并把它加到标记队列(该对象变为灰色)
  32.  
    // Same work as in scanobject; see comments there.
  33.  
    obj := *(*uintptr)(unsafe.Pointer(b + i))
  34.  
    if obj != 0 && arena_start <= obj && obj < arena_used {
  35.  
    // 找到该对象对应的span和bitmap
  36.  
    if obj, hbits, span, objIndex := heapBitsForObject(obj, b, i); obj != 0 {
  37.  
    // 标记一个对象存活, 并把它加到标记队列(该对象变为灰色)
  38.  
    greyobject(obj, b, i, hbits, span, gcw, objIndex)
  39.  
    }
  40.  
    }
  41.  
    }
  42.  
    // 处理下一个指针下一个bit
  43.  
    bits >>= 1
  44.  
    i += sys.PtrSize
  45.  
    }
  46.  
    }
  47.  
    }

greyobject用于标记一个对象存活, 并把它加到标记队列(该对象变为灰色):

  1.  
    // obj is the start of an object with mark mbits.
  2.  
    // If it isn't already marked, mark it and enqueue into gcw.
  3.  
    // base and off are for debugging only and could be removed.
  4.  
    //go:nowritebarrierrec
  5.  
    func greyobject(obj, base, off uintptr, hbits heapBits, span *mspan, gcw *gcWork, objIndex uintptr) {
  6.  
    // obj should be start of allocation, and so must be at least pointer-aligned.
  7.  
    if obj&(sys.PtrSize-1) != 0 {
  8.  
    throw("greyobject: obj not pointer-aligned")
  9.  
    }
  10.  
    mbits := span.markBitsForIndex(objIndex)
  11.  
     
  12.  
    if useCheckmark {
  13.  
    // checkmark是用于检查是否所有可到达的对象都被正确标记的机制, 仅除错使用
  14.  
    if !mbits.isMarked() {
  15.  
    printlock()
  16.  
    print("runtime:greyobject: checkmarks finds unexpected unmarked object obj=", hex(obj), "\n")
  17.  
    print("runtime: found obj at *(", hex(base), "+", hex(off), ")\n")
  18.  
     
  19.  
    // Dump the source (base) object
  20.  
    gcDumpObject("base", base, off)
  21.  
     
  22.  
    // Dump the object
  23.  
    gcDumpObject("obj", obj, ^uintptr(0))
  24.  
     
  25.  
    getg().m.traceback = 2
  26.  
    throw("checkmark found unmarked object")
  27.  
    }
  28.  
    if hbits.isCheckmarked(span.elemsize) {
  29.  
    return
  30.  
    }
  31.  
    hbits.setCheckmarked(span.elemsize)
  32.  
    if !hbits.isCheckmarked(span.elemsize) {
  33.  
    throw("setCheckmarked and isCheckmarked disagree")
  34.  
    }
  35.  
    } else {
  36.  
    if debug.gccheckmark > 0 && span.isFree(objIndex) {
  37.  
    print("runtime: marking free object ", hex(obj), " found at *(", hex(base), "+", hex(off), ")\n")
  38.  
    gcDumpObject("base", base, off)
  39.  
    gcDumpObject("obj", obj, ^uintptr(0))
  40.  
    getg().m.traceback = 2
  41.  
    throw("marking free object")
  42.  
    }
  43.  
     
  44.  
    // 如果对象所在的span中的gcmarkBits对应的bit已经设置为1则可以跳过处理
  45.  
    // If marked we have nothing to do.
  46.  
    if mbits.isMarked() {
  47.  
    return
  48.  
    }
  49.  
     
  50.  
    // 设置对象所在的span中的gcmarkBits对应的bit为1
  51.  
    // mbits.setMarked() // Avoid extra call overhead with manual inlining.
  52.  
    atomic.Or8(mbits.bytep, mbits.mask)
  53.  
     
  54.  
    // 如果确定对象不包含指针(所在span的类型是noscan), 则不需要把对象放入标记队列
  55.  
    // If this is a noscan object, fast-track it to black
  56.  
    // instead of greying it.
  57.  
    if span.spanclass.noscan() {
  58.  
    gcw.bytesMarked += uint64(span.elemsize)
  59.  
    return
  60.  
    }
  61.  
    }
  62.  
     
  63.  
    // 把对象放入标记队列
  64.  
    // 先放入本地标记队列, 失败时把本地标记队列中的部分工作转移到全局标记队列, 再放入本地标记队列
  65.  
    // Queue the obj for scanning. The PREFETCH(obj) logic has been removed but
  66.  
    // seems like a nice optimization that can be added back in.
  67.  
    // There needs to be time between the PREFETCH and the use.
  68.  
    // Previously we put the obj in an 8 element buffer that is drained at a rate
  69.  
    // to give the PREFETCH time to do its work.
  70.  
    // Use of PREFETCHNTA might be more appropriate than PREFETCH
  71.  
    if !gcw.putFast(obj) {
  72.  
    gcw.put(obj)
  73.  
    }
  74.  
    }

gcDrain函数扫描完根对象, 就会开始消费标记队列, 对从标记队列中取出的对象调用scanobject函数:

  1.  
    // scanobject scans the object starting at b, adding pointers to gcw.
  2.  
    // b must point to the beginning of a heap object or an oblet.
  3.  
    // scanobject consults the GC bitmap for the pointer mask and the
  4.  
    // spans for the size of the object.
  5.  
    //
  6.  
    //go:nowritebarrier
  7.  
    func scanobject(b uintptr, gcw *gcWork) {
  8.  
    // Note that arena_used may change concurrently during
  9.  
    // scanobject and hence scanobject may encounter a pointer to
  10.  
    // a newly allocated heap object that is *not* in
  11.  
    // [start,used). It will not mark this object; however, we
  12.  
    // know that it was just installed by a mutator, which means
  13.  
    // that mutator will execute a write barrier and take care of
  14.  
    // marking it. This is even more pronounced on relaxed memory
  15.  
    // architectures since we access arena_used without barriers
  16.  
    // or synchronization, but the same logic applies.
  17.  
    arena_start := mheap_.arena_start
  18.  
    arena_used := mheap_.arena_used
  19.  
     
  20.  
    // Find the bits for b and the size of the object at b.
  21.  
    //
  22.  
    // b is either the beginning of an object, in which case this
  23.  
    // is the size of the object to scan, or it points to an
  24.  
    // oblet, in which case we compute the size to scan below.
  25.  
    // 获取对象对应的bitmap
  26.  
    hbits := heapBitsForAddr(b)
  27.  
     
  28.  
    // 获取对象所在的span
  29.  
    s := spanOfUnchecked(b)
  30.  
     
  31.  
    // 获取对象的大小
  32.  
    n := s.elemsize
  33.  
    if n == 0 {
  34.  
    throw("scanobject n == 0")
  35.  
    }
  36.  
     
  37.  
    // 对象大小过大时(maxObletBytes是128KB)需要分割扫描
  38.  
    // 每次最多只扫描128KB
  39.  
    if n > maxObletBytes {
  40.  
    // Large object. Break into oblets for better
  41.  
    // parallelism and lower latency.
  42.  
    if b == s.base() {
  43.  
    // It's possible this is a noscan object (not
  44.  
    // from greyobject, but from other code
  45.  
    // paths), in which case we must *not* enqueue
  46.  
    // oblets since their bitmaps will be
  47.  
    // uninitialized.
  48.  
    if s.spanclass.noscan() {
  49.  
    // Bypass the whole scan.
  50.  
    gcw.bytesMarked += uint64(n)
  51.  
    return
  52.  
    }
  53.  
     
  54.  
    // Enqueue the other oblets to scan later.
  55.  
    // Some oblets may be in b's scalar tail, but
  56.  
    // these will be marked as "no more pointers",
  57.  
    // so we'll drop out immediately when we go to
  58.  
    // scan those.
  59.  
    for oblet := b + maxObletBytes; oblet < s.base()+s.elemsize; oblet += maxObletBytes {
  60.  
    if !gcw.putFast(oblet) {
  61.  
    gcw.put(oblet)
  62.  
    }
  63.  
    }
  64.  
    }
  65.  
     
  66.  
    // Compute the size of the oblet. Since this object
  67.  
    // must be a large object, s.base() is the beginning
  68.  
    // of the object.
  69.  
    n = s.base() + s.elemsize - b
  70.  
    if n > maxObletBytes {
  71.  
    n = maxObletBytes
  72.  
    }
  73.  
    }
  74.  
     
  75.  
    // 扫描对象中的指针
  76.  
    var i uintptr
  77.  
    for i = 0; i < n; i += sys.PtrSize {
  78.  
    // 获取对应的bit
  79.  
    // Find bits for this word.
  80.  
    if i != 0 {
  81.  
    // Avoid needless hbits.next() on last iteration.
  82.  
    hbits = hbits.next()
  83.  
    }
  84.  
    // Load bits once. See CL 22712 and issue 16973 for discussion.
  85.  
    bits := hbits.bits()
  86.  
     
  87.  
    // 检查scan bit判断是否继续扫描, 注意第二个scan bit是checkmark
  88.  
    // During checkmarking, 1-word objects store the checkmark
  89.  
    // in the type bit for the one word. The only one-word objects
  90.  
    // are pointers, or else they'd be merged with other non-pointer
  91.  
    // data into larger allocations.
  92.  
    if i != 1*sys.PtrSize && bits&bitScan == 0 {
  93.  
    break // no more pointers in this object
  94.  
    }
  95.  
     
  96.  
    // 检查pointer bit, 不是指针则继续
  97.  
    if bits&bitPointer == 0 {
  98.  
    continue // not a pointer
  99.  
    }
  100.  
     
  101.  
    // 取出指针的值
  102.  
    // Work here is duplicated in scanblock and above.
  103.  
    // If you make changes here, make changes there too.
  104.  
    obj := *(*uintptr)(unsafe.Pointer(b + i))
  105.  
     
  106.  
    // 如果指针在arena区域中, 则调用greyobject标记对象并把对象放到标记队列中
  107.  
    // At this point we have extracted the next potential pointer.
  108.  
    // Check if it points into heap and not back at the current object.
  109.  
    if obj != 0 && arena_start <= obj && obj < arena_used && obj-b >= n {
  110.  
    // Mark the object.
  111.  
    if obj, hbits, span, objIndex := heapBitsForObject(obj, b, i); obj != 0 {
  112.  
    greyobject(obj, b, i, hbits, span, gcw, objIndex)
  113.  
    }
  114.  
    }
  115.  
    }
  116.  
     
  117.  
    // 统计扫描过的大小和对象数量
  118.  
    gcw.bytesMarked += uint64(n)
  119.  
    gcw.scanWork += int64(i)
  120.  
    }

在所有后台标记任务都把标记队列消费完毕时, 会执行gcMarkDone函数准备进入完成标记阶段(mark termination):
在并行GC中gcMarkDone会被执行两次, 第一次会禁止本地标记队列然后重新开始后台标记任务, 第二次会进入完成标记阶段(mark termination)。

  1.  
    // gcMarkDone transitions the GC from mark 1 to mark 2 and from mark 2
  2.  
    // to mark termination.
  3.  
    //
  4.  
    // This should be called when all mark work has been drained. In mark
  5.  
    // 1, this includes all root marking jobs, global work buffers, and
  6.  
    // active work buffers in assists and background workers; however,
  7.  
    // work may still be cached in per-P work buffers. In mark 2, per-P
  8.  
    // caches are disabled.
  9.  
    //
  10.  
    // The calling context must be preemptible.
  11.  
    //
  12.  
    // Note that it is explicitly okay to have write barriers in this
  13.  
    // function because completion of concurrent mark is best-effort
  14.  
    // anyway. Any work created by write barriers here will be cleaned up
  15.  
    // by mark termination.
  16.  
    func gcMarkDone() {
  17.  
    top:
  18.  
    semacquire(&work.markDoneSema)
  19.  
     
  20.  
    // Re-check transition condition under transition lock.
  21.  
    if !(gcphase == _GCmark && work.nwait == work.nproc && !gcMarkWorkAvailable(nil)) {
  22.  
    semrelease(&work.markDoneSema)
  23.  
    return
  24.  
    }
  25.  
     
  26.  
    // 暂时禁止启动新的后台标记任务
  27.  
    // Disallow starting new workers so that any remaining workers
  28.  
    // in the current mark phase will drain out.
  29.  
    //
  30.  
    // TODO(austin): Should dedicated workers keep an eye on this
  31.  
    // and exit gcDrain promptly?
  32.  
    atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, -0xffffffff)
  33.  
    atomic.Xaddint64(&gcController.fractionalMarkWorkersNeeded, -0xffffffff)
  34.  
     
  35.  
    // 判断本地标记队列是否已禁用
  36.  
    if !gcBlackenPromptly {
  37.  
    // 本地标记队列是否未禁用, 禁用然后重新开始后台标记任务
  38.  
    // Transition from mark 1 to mark 2.
  39.  
    //
  40.  
    // The global work list is empty, but there can still be work
  41.  
    // sitting in the per-P work caches.
  42.  
    // Flush and disable work caches.
  43.  
     
  44.  
    // 禁用本地标记队列
  45.  
    // Disallow caching workbufs and indicate that we're in mark 2.
  46.  
    gcBlackenPromptly = true
  47.  
     
  48.  
    // Prevent completion of mark 2 until we've flushed
  49.  
    // cached workbufs.
  50.  
    atomic.Xadd(&work.nwait, -1)
  51.  
     
  52.  
    // GC is set up for mark 2. Let Gs blocked on the
  53.  
    // transition lock go while we flush caches.
  54.  
    semrelease(&work.markDoneSema)
  55.  
     
  56.  
    // 把所有本地标记队列中的对象都推到全局标记队列
  57.  
    systemstack(func() {
  58.  
    // Flush all currently cached workbufs and
  59.  
    // ensure all Ps see gcBlackenPromptly. This
  60.  
    // also blocks until any remaining mark 1
  61.  
    // workers have exited their loop so we can
  62.  
    // start new mark 2 workers.
  63.  
    forEachP(func(_p_ *p) {
  64.  
    _p_.gcw.dispose()
  65.  
    })
  66.  
    })
  67.  
     
  68.  
    // 除错用
  69.  
    // Check that roots are marked. We should be able to
  70.  
    // do this before the forEachP, but based on issue
  71.  
    // #16083 there may be a (harmless) race where we can
  72.  
    // enter mark 2 while some workers are still scanning
  73.  
    // stacks. The forEachP ensures these scans are done.
  74.  
    //
  75.  
    // TODO(austin): Figure out the race and fix this
  76.  
    // properly.
  77.  
    gcMarkRootCheck()
  78.  
     
  79.  
    // 允许启动新的后台标记任务
  80.  
    // Now we can start up mark 2 workers.
  81.  
    atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, 0xffffffff)
  82.  
    atomic.Xaddint64(&gcController.fractionalMarkWorkersNeeded, 0xffffffff)
  83.  
     
  84.  
    // 如果确定没有更多的任务则可以直接跳到函数顶部
  85.  
    // 这样就当作是第二次调用了
  86.  
    incnwait := atomic.Xadd(&work.nwait, +1)
  87.  
    if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {
  88.  
    // This loop will make progress because
  89.  
    // gcBlackenPromptly is now true, so it won't
  90.  
    // take this same "if" branch.
  91.  
    goto top
  92.  
    }
  93.  
    } else {
  94.  
    // 记录完成标记阶段开始的时间和STW开始的时间
  95.  
    // Transition to mark termination.
  96.  
    now := nanotime()
  97.  
    work.tMarkTerm = now
  98.  
    work.pauseStart = now
  99.  
     
  100.  
    // 禁止G被抢占
  101.  
    getg().m.preemptoff = "gcing"
  102.  
     
  103.  
    // 停止所有运行中的G, 并禁止它们运行
  104.  
    systemstack(stopTheWorldWithSema)
  105.  
     
  106.  
    // !!!!!!!!!!!!!!!!
  107.  
    // 世界已停止(STW)...
  108.  
    // !!!!!!!!!!!!!!!!
  109.  
     
  110.  
    // The gcphase is _GCmark, it will transition to _GCmarktermination
  111.  
    // below. The important thing is that the wb remains active until
  112.  
    // all marking is complete. This includes writes made by the GC.
  113.  
     
  114.  
    // 标记对根对象的扫描已完成, 会影响gcMarkRootPrepare中的处理
  115.  
    // Record that one root marking pass has completed.
  116.  
    work.markrootDone = true
  117.  
     
  118.  
    // 禁止辅助GC和后台标记任务的运行
  119.  
    // Disable assists and background workers. We must do
  120.  
    // this before waking blocked assists.
  121.  
    atomic.Store(&gcBlackenEnabled, 0)
  122.  
     
  123.  
    // 唤醒所有因为辅助GC而休眠的G
  124.  
    // Wake all blocked assists. These will run when we
  125.  
    // start the world again.
  126.  
    gcWakeAllAssists()
  127.  
     
  128.  
    // Likewise, release the transition lock. Blocked
  129.  
    // workers and assists will run when we start the
  130.  
    // world again.
  131.  
    semrelease(&work.markDoneSema)
  132.  
     
  133.  
    // 计算下一次触发gc需要的heap大小
  134.  
    // endCycle depends on all gcWork cache stats being
  135.  
    // flushed. This is ensured by mark 2.
  136.  
    nextTriggerRatio := gcController.endCycle()
  137.  
     
  138.  
    // 进入完成标记阶段, 会重新启动世界
  139.  
    // Perform mark termination. This will restart the world.
  140.  
    gcMarkTermination(nextTriggerRatio)
  141.  
    }
  142.  
    }
  143.  
     

gcMarkTermination函数会进入完成标记阶段:

  1.  
    func gcMarkTermination(nextTriggerRatio float64) {
  2.  
    // World is stopped.
  3.  
    // Start marktermination which includes enabling the write barrier.
  4.  
    // 禁止辅助GC和后台标记任务的运行
  5.  
    atomic.Store(&gcBlackenEnabled, 0)
  6.  
     
  7.  
    // 重新允许本地标记队列(下次GC使用)
  8.  
    gcBlackenPromptly = false
  9.  
     
  10.  
    // 设置当前GC阶段到完成标记阶段, 并启用写屏障
  11.  
    setGCPhase(_GCmarktermination)
  12.  
     
  13.  
    // 记录开始时间
  14.  
    work.heap1 = memstats.heap_live
  15.  
    startTime := nanotime()
  16.  
     
  17.  
    // 禁止G被抢占
  18.  
    mp := acquirem()
  19.  
    mp.preemptoff = "gcing"
  20.  
    _g_ := getg()
  21.  
    _g_.m.traceback = 2
  22.  
     
  23.  
    // 设置G的状态为等待中这样它的栈可以被扫描
  24.  
    gp := _g_.m.curg
  25.  
    casgstatus(gp, _Grunning, _Gwaiting)
  26.  
    gp.waitreason = "garbage collection"
  27.  
     
  28.  
    // 切换到g0运行
  29.  
    // Run gc on the g0 stack. We do this so that the g stack
  30.  
    // we're currently running on will no longer change. Cuts
  31.  
    // the root set down a bit (g0 stacks are not scanned, and
  32.  
    // we don't need to scan gc's internal state). We also
  33.  
    // need to switch to g0 so we can shrink the stack.
  34.  
    systemstack(func() {
  35.  
    // 开始STW中的标记
  36.  
    gcMark(startTime)
  37.  
     
  38.  
    // 必须立刻返回, 因为外面的G的栈有可能被移动, 不能在这之后访问外面的变量
  39.  
    // Must return immediately.
  40.  
    // The outer function's stack may have moved
  41.  
    // during gcMark (it shrinks stacks, including the
  42.  
    // outer function's stack), so we must not refer
  43.  
    // to any of its variables. Return back to the
  44.  
    // non-system stack to pick up the new addresses
  45.  
    // before continuing.
  46.  
    })
  47.  
     
  48.  
    // 重新切换到g0运行
  49.  
    systemstack(func() {
  50.  
    work.heap2 = work.bytesMarked
  51.  
     
  52.  
    // 如果启用了checkmark则执行检查, 检查是否所有可到达的对象都有标记
  53.  
    if debug.gccheckmark > 0 {
  54.  
    // Run a full stop-the-world mark using checkmark bits,
  55.  
    // to check that we didn't forget to mark anything during
  56.  
    // the concurrent mark process.
  57.  
    gcResetMarkState()
  58.  
    initCheckmarks()
  59.  
    gcMark(startTime)
  60.  
    clearCheckmarks()
  61.  
    }
  62.  
     
  63.  
    // 设置当前GC阶段到关闭, 并禁用写屏障
  64.  
    // marking is complete so we can turn the write barrier off
  65.  
    setGCPhase(_GCoff)
  66.  
     
  67.  
    // 唤醒后台清扫任务, 将在STW结束后开始运行
  68.  
    gcSweep(work.mode)
  69.  
     
  70.  
    // 除错用
  71.  
    if debug.gctrace > 1 {
  72.  
    startTime = nanotime()
  73.  
    // The g stacks have been scanned so
  74.  
    // they have gcscanvalid==true and gcworkdone==true.
  75.  
    // Reset these so that all stacks will be rescanned.
  76.  
    gcResetMarkState()
  77.  
    finishsweep_m()
  78.  
     
  79.  
    // Still in STW but gcphase is _GCoff, reset to _GCmarktermination
  80.  
    // At this point all objects will be found during the gcMark which
  81.  
    // does a complete STW mark and object scan.
  82.  
    setGCPhase(_GCmarktermination)
  83.  
    gcMark(startTime)
  84.  
    setGCPhase(_GCoff) // marking is done, turn off wb.
  85.  
    gcSweep(work.mode)
  86.  
    }
  87.  
    })
  88.  
     
  89.  
    // 设置G的状态为运行中
  90.  
    _g_.m.traceback = 0
  91.  
    casgstatus(gp, _Gwaiting, _Grunning)
  92.  
     
  93.  
    // 跟踪处理
  94.  
    if trace.enabled {
  95.  
    traceGCDone()
  96.  
    }
  97.  
     
  98.  
    // all done
  99.  
    mp.preemptoff = ""
  100.  
     
  101.  
    if gcphase != _GCoff {
  102.  
    throw("gc done but gcphase != _GCoff")
  103.  
    }
  104.  
     
  105.  
    // 更新下一次触发gc需要的heap大小(gc_trigger)
  106.  
    // Update GC trigger and pacing for the next cycle.
  107.  
    gcSetTriggerRatio(nextTriggerRatio)
  108.  
     
  109.  
    // 更新用时记录
  110.  
    // Update timing memstats
  111.  
    now := nanotime()
  112.  
    sec, nsec, _ := time_now()
  113.  
    unixNow := sec*1e9 + int64(nsec)
  114.  
    work.pauseNS += now - work.pauseStart
  115.  
    work.tEnd = now
  116.  
    atomic.Store64(&memstats.last_gc_unix, uint64(unixNow)) // must be Unix time to make sense to user
  117.  
    atomic.Store64(&memstats.last_gc_nanotime, uint64(now)) // monotonic time for us
  118.  
    memstats.pause_ns[memstats.numgc%uint32(len(memstats.pause_ns))] = uint64(work.pauseNS)
  119.  
    memstats.pause_end[memstats.numgc%uint32(len(memstats.pause_end))] = uint64(unixNow)
  120.  
    memstats.pause_total_ns += uint64(work.pauseNS)
  121.  
     
  122.  
    // 更新所用cpu记录
  123.  
    // Update work.totaltime.
  124.  
    sweepTermCpu := int64(work.stwprocs) * (work.tMark - work.tSweepTerm)
  125.  
    // We report idle marking time below, but omit it from the
  126.  
    // overall utilization here since it's "free".
  127.  
    markCpu := gcController.assistTime + gcController.dedicatedMarkTime + gcController.fractionalMarkTime
  128.  
    markTermCpu := int64(work.stwprocs) * (work.tEnd - work.tMarkTerm)
  129.  
    cycleCpu := sweepTermCpu + markCpu + markTermCpu
  130.  
    work.totaltime += cycleCpu
  131.  
     
  132.  
    // Compute overall GC CPU utilization.
  133.  
    totalCpu := sched.totaltime + (now-sched.procresizetime)*int64(gomaxprocs)
  134.  
    memstats.gc_cpu_fraction = float64(work.totaltime) / float64(totalCpu)
  135.  
     
  136.  
    // 重置清扫状态
  137.  
    // Reset sweep state.
  138.  
    sweep.nbgsweep = 0
  139.  
    sweep.npausesweep = 0
  140.  
     
  141.  
    // 统计强制开始GC的次数
  142.  
    if work.userForced {
  143.  
    memstats.numforcedgc++
  144.  
    }
  145.  
     
  146.  
    // 统计执行GC的次数然后唤醒等待清扫的G
  147.  
    // Bump GC cycle count and wake goroutines waiting on sweep.
  148.  
    lock(&work.sweepWaiters.lock)
  149.  
    memstats.numgc++
  150.  
    injectglist(work.sweepWaiters.head.ptr())
  151.  
    work.sweepWaiters.head = 0
  152.  
    unlock(&work.sweepWaiters.lock)
  153.  
     
  154.  
    // 性能统计用
  155.  
    // Finish the current heap profiling cycle and start a new
  156.  
    // heap profiling cycle. We do this before starting the world
  157.  
    // so events don't leak into the wrong cycle.
  158.  
    mProf_NextCycle()
  159.  
     
  160.  
    // 重新启动世界
  161.  
    systemstack(startTheWorldWithSema)
  162.  
     
  163.  
    // !!!!!!!!!!!!!!!
  164.  
    // 世界已重新启动...
  165.  
    // !!!!!!!!!!!!!!!
  166.  
     
  167.  
    // 性能统计用
  168.  
    // Flush the heap profile so we can start a new cycle next GC.
  169.  
    // This is relatively expensive, so we don't do it with the
  170.  
    // world stopped.
  171.  
    mProf_Flush()
  172.  
     
  173.  
    // 移动标记队列使用的缓冲区到自由列表, 使得它们可以被回收
  174.  
    // Prepare workbufs for freeing by the sweeper. We do this
  175.  
    // asynchronously because it can take non-trivial time.
  176.  
    prepareFreeWorkbufs()
  177.  
     
  178.  
    // 释放未使用的栈
  179.  
    // Free stack spans. This must be done between GC cycles.
  180.  
    systemstack(freeStackSpans)
  181.  
     
  182.  
    // 除错用
  183.  
    // Print gctrace before dropping worldsema. As soon as we drop
  184.  
    // worldsema another cycle could start and smash the stats
  185.  
    // we're trying to print.
  186.  
    if debug.gctrace > 0 {
  187.  
    util := int(memstats.gc_cpu_fraction * 100)
  188.  
     
  189.  
    var sbuf [24]byte
  190.  
    printlock()
  191.  
    print("gc ", memstats.numgc,
  192.  
    " @", string(itoaDiv(sbuf[:], uint64(work.tSweepTerm-runtimeInitTime)/1e6, 3)), "s ",
  193.  
    util, "%: ")
  194.  
    prev := work.tSweepTerm
  195.  
    for i, ns := range []int64{work.tMark, work.tMarkTerm, work.tEnd} {
  196.  
    if i != 0 {
  197.  
    print("+")
  198.  
    }
  199.  
    print(string(fmtNSAsMS(sbuf[:], uint64(ns-prev))))
  200.  
    prev = ns
  201.  
    }
  202.  
    print(" ms clock, ")
  203.  
    for i, ns := range []int64{sweepTermCpu, gcController.assistTime, gcController.dedicatedMarkTime + gcController.fractionalMarkTime, gcController.idleMarkTime, markTermCpu} {
  204.  
    if i == 2 || i == 3 {
  205.  
    // Separate mark time components with /.
  206.  
    print("/")
  207.  
    } else if i != 0 {
  208.  
    print("+")
  209.  
    }
  210.  
    print(string(fmtNSAsMS(sbuf[:], uint64(ns))))
  211.  
    }
  212.  
    print(" ms cpu, ",
  213.  
    work.heap0>>20, "->", work.heap1>>20, "->", work.heap2>>20, " MB, ",
  214.  
    work.heapGoal>>20, " MB goal, ",
  215.  
    work.maxprocs, " P")
  216.  
    if work.userForced {
  217.  
    print(" (forced)")
  218.  
    }
  219.  
    print("\n")
  220.  
    printunlock()
  221.  
    }
  222.  
     
  223.  
    semrelease(&worldsema)
  224.  
    // Careful: another GC cycle may start now.
  225.  
     
  226.  
    // 重新允许当前的G被抢占
  227.  
    releasem(mp)
  228.  
    mp = nil
  229.  
     
  230.  
    // 如果是并行GC, 让当前M继续运行(会回到gcBgMarkWorker然后休眠)
  231.  
    // 如果不是并行GC, 则让当前M开始调度
  232.  
    // now that gc is done, kick off finalizer thread if needed
  233.  
    if !concurrentSweep {
  234.  
    // give the queued finalizers, if any, a chance to run
  235.  
    Gosched()
  236.  
    }
  237.  
    }

gcSweep函数会唤醒后台清扫任务:

后台清扫任务会在程序启动时调用的gcenable函数中启动.

  1.  
    func gcSweep(mode gcMode) {
  2.  
    if gcphase != _GCoff {
  3.  
    throw("gcSweep being done but phase is not GCoff")
  4.  
    }
  5.  
     
  6.  
    // 增加sweepgen, 这样sweepSpans中两个队列角色会交换, 所有span都会变为"待清扫"的span
  7.  
    lock(&mheap_.lock)
  8.  
    mheap_.sweepgen += 2
  9.  
    mheap_.sweepdone = 0
  10.  
    if mheap_.sweepSpans[mheap_.sweepgen/2%2].index != 0 {
  11.  
    // We should have drained this list during the last
  12.  
    // sweep phase. We certainly need to start this phase
  13.  
    // with an empty swept list.
  14.  
    throw("non-empty swept list")
  15.  
    }
  16.  
    mheap_.pagesSwept = 0
  17.  
    unlock(&mheap_.lock)
  18.  
     
  19.  
    // 如果非并行GC则在这里完成所有工作(STW中)
  20.  
    if !_ConcurrentSweep || mode == gcForceBlockMode {
  21.  
    // Special case synchronous sweep.
  22.  
    // Record that no proportional sweeping has to happen.
  23.  
    lock(&mheap_.lock)
  24.  
    mheap_.sweepPagesPerByte = 0
  25.  
    unlock(&mheap_.lock)
  26.  
    // Sweep all spans eagerly.
  27.  
    for sweepone() != ^uintptr(0) {
  28.  
    sweep.npausesweep++
  29.  
    }
  30.  
    // Free workbufs eagerly.
  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.  
     
  42.  
    // 唤醒后台清扫任务
  43.  
    // Background sweep.
  44.  
    lock(&sweep.lock)
  45.  
    if sweep.parked {
  46.  
    sweep.parked = false
  47.  
    ready(sweep.g, 0, true)
  48.  
    }
  49.  
    unlock(&sweep.lock)
  50.  
    }

后台清扫任务的函数是bgsweep:

  1.  
    func bgsweep(c chan int) {
  2.  
    sweep.g = getg()
  3.  
     
  4.  
    // 等待唤醒
  5.  
    lock(&sweep.lock)
  6.  
    sweep.parked = true
  7.  
    c <- 1
  8.  
    goparkunlock(&sweep.lock, "GC sweep wait", traceEvGoBlock, 1)
  9.  
     
  10.  
    // 循环清扫
  11.  
    for {
  12.  
    // 清扫一个span, 然后进入调度(一次只做少量工作)
  13.  
    for gosweepone() != ^uintptr(0) {
  14.  
    sweep.nbgsweep++
  15.  
    Gosched()
  16.  
    }
  17.  
    // 释放一些未使用的标记队列缓冲区到heap
  18.  
    for freeSomeWbufs(true) {
  19.  
    Gosched()
  20.  
    }
  21.  
    // 如果清扫未完成则继续循环
  22.  
    lock(&sweep.lock)
  23.  
    if !gosweepdone() {
  24.  
    // This can happen if a GC runs between
  25.  
    // gosweepone returning ^0 above
  26.  
    // and the lock being acquired.
  27.  
    unlock(&sweep.lock)
  28.  
    continue
  29.  
    }
  30.  
    // 否则让后台清扫任务进入休眠, 当前M继续调度
  31.  
    sweep.parked = true
  32.  
    goparkunlock(&sweep.lock, "GC sweep wait", traceEvGoBlock, 1)
  33.  
    }
  34.  
    }

gosweepone函数会从sweepSpans中取出单个span清扫:

  1.  
    //go:nowritebarrier
  2.  
    func gosweepone() uintptr {
  3.  
    var ret uintptr
  4.  
    // 切换到g0运行
  5.  
    systemstack(func() {
  6.  
    ret = sweepone()
  7.  
    })
  8.  
    return ret
  9.  
    }

sweepone函数如下:

  1.  
    // sweeps one span
  2.  
    // returns number of pages returned to heap, or ^uintptr(0) if there is nothing to sweep
  3.  
    //go:nowritebarrier
  4.  
    func sweepone() uintptr {
  5.  
    _g_ := getg()
  6.  
    sweepRatio := mheap_.sweepPagesPerByte // For debugging
  7.  
     
  8.  
    // 禁止G被抢占
  9.  
    // increment locks to ensure that the goroutine is not preempted
  10.  
    // in the middle of sweep thus leaving the span in an inconsistent state for next GC
  11.  
    _g_.m.locks++
  12.  
     
  13.  
    // 检查是否已完成清扫
  14.  
    if atomic.Load(&mheap_.sweepdone) != 0 {
  15.  
    _g_.m.locks--
  16.  
    return ^uintptr(0)
  17.  
    }
  18.  
     
  19.  
    // 更新同时执行sweep的任务数量
  20.  
    atomic.Xadd(&mheap_.sweepers, +1)
  21.  
     
  22.  
    npages := ^uintptr(0)
  23.  
    sg := mheap_.sweepgen
  24.  
    for {
  25.  
    // 从sweepSpans中取出一个span
  26.  
    s := mheap_.sweepSpans[1-sg/2%2].pop()
  27.  
    // 全部清扫完毕时跳出循环
  28.  
    if s == nil {
  29.  
    atomic.Store(&mheap_.sweepdone, 1)
  30.  
    break
  31.  
    }
  32.  
    // 其他M已经在清扫这个span时跳过
  33.  
    if s.state != mSpanInUse {
  34.  
    // This can happen if direct sweeping already
  35.  
    // swept this span, but in that case the sweep
  36.  
    // generation should always be up-to-date.
  37.  
    if s.sweepgen != sg {
  38.  
    print("runtime: bad span s.state=", s.state, " s.sweepgen=", s.sweepgen, " sweepgen=", sg, "\n")
  39.  
    throw("non in-use span in unswept list")
  40.  
    }
  41.  
    continue
  42.  
    }
  43.  
    // 原子增加span的sweepgen, 失败表示其他M已经开始清扫这个span, 跳过
  44.  
    if s.sweepgen != sg-2 || !atomic.Cas(&s.sweepgen, sg-2, sg-1) {
  45.  
    continue
  46.  
    }
  47.  
    // 清扫这个span, 然后跳出循环
  48.  
    npages = s.npages
  49.  
    if !s.sweep(false) {
  50.  
    // Span is still in-use, so this returned no
  51.  
    // pages to the heap and the span needs to
  52.  
    // move to the swept in-use list.
  53.  
    npages = 0
  54.  
    }
  55.  
    break
  56.  
    }
  57.  
     
  58.  
    // 更新同时执行sweep的任务数量
  59.  
    // Decrement the number of active sweepers and if this is the
  60.  
    // last one print trace information.
  61.  
    if atomic.Xadd(&mheap_.sweepers, -1) == 0 && atomic.Load(&mheap_.sweepdone) != 0 {
  62.  
    if debug.gcpacertrace > 0 {
  63.  
    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")
  64.  
    }
  65.  
    }
  66.  
    // 允许G被抢占
  67.  
    _g_.m.locks--
  68.  
    // 返回清扫的页数
  69.  
    return npages
  70.  
    }

span的sweep函数用于清扫单个span:

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

从bgsweep和前面的分配器可以看出扫描阶段的工作是十分懒惰(lazy)的,
实际可能会出现前一阶段的扫描还未完成, 就需要开始新一轮的GC的情况,
所以每一轮GC开始之前都需要完成前一轮GC的扫描工作(Sweep Termination阶段).

GC的整个流程都分析完毕了, 最后贴上写屏障函数writebarrierptr的实现:

  1.  
    // NOTE: Really dst *unsafe.Pointer, src unsafe.Pointer,
  2.  
    // but if we do that, Go inserts a write barrier on *dst = src.
  3.  
    //go:nosplit
  4.  
    func writebarrierptr(dst *uintptr, src uintptr) {
  5.  
    if writeBarrier.cgo {
  6.  
    cgoCheckWriteBarrier(dst, src)
  7.  
    }
  8.  
    if !writeBarrier.needed {
  9.  
    *dst = src
  10.  
    return
  11.  
    }
  12.  
    if src != 0 && src < minPhysPageSize {
  13.  
    systemstack(func() {
  14.  
    print("runtime: writebarrierptr *", dst, " = ", hex(src), "\n")
  15.  
    throw("bad pointer in write barrier")
  16.  
    })
  17.  
    }
  18.  
    // 标记指针
  19.  
    writebarrierptr_prewrite1(dst, src)
  20.  
    // 设置指针到目标
  21.  
    *dst = src
  22.  
    }

writebarrierptr_prewrite1函数如下:

  1.  
    // writebarrierptr_prewrite1 invokes a write barrier for *dst = src
  2.  
    // prior to the write happening.
  3.  
    //
  4.  
    // Write barrier calls must not happen during critical GC and scheduler
  5.  
    // related operations. In particular there are times when the GC assumes
  6.  
    // that the world is stopped but scheduler related code is still being
  7.  
    // executed, dealing with syscalls, dealing with putting gs on runnable
  8.  
    // queues and so forth. This code cannot execute write barriers because
  9.  
    // the GC might drop them on the floor. Stopping the world involves removing
  10.  
    // the p associated with an m. We use the fact that m.p == nil to indicate
  11.  
    // that we are in one these critical section and throw if the write is of
  12.  
    // a pointer to a heap object.
  13.  
    //go:nosplit
  14.  
    func writebarrierptr_prewrite1(dst *uintptr, src uintptr) {
  15.  
    mp := acquirem()
  16.  
    if mp.inwb || mp.dying > 0 {
  17.  
    releasem(mp)
  18.  
    return
  19.  
    }
  20.  
    systemstack(func() {
  21.  
    if mp.p == 0 && memstats.enablegc && !mp.inwb && inheap(src) {
  22.  
    throw("writebarrierptr_prewrite1 called with mp.p == nil")
  23.  
    }
  24.  
    mp.inwb = true
  25.  
    gcmarkwb_m(dst, src)
  26.  
    })
  27.  
    mp.inwb = false
  28.  
    releasem(mp)
  29.  
    }

gcmarkwb_m函数如下:

  1.  
    func gcmarkwb_m(slot *uintptr, ptr uintptr) {
  2.  
    if writeBarrier.needed {
  3.  
    // Note: This turns bad pointer writes into bad
  4.  
    // pointer reads, which could be confusing. We avoid
  5.  
    // reading from obviously bad pointers, which should
  6.  
    // take care of the vast majority of these. We could
  7.  
    // patch this up in the signal handler, or use XCHG to
  8.  
    // combine the read and the write. Checking inheap is
  9.  
    // insufficient since we need to track changes to
  10.  
    // roots outside the heap.
  11.  
    //
  12.  
    // Note: profbuf.go omits a barrier during signal handler
  13.  
    // profile logging; that's safe only because this deletion barrier exists.
  14.  
    // If we remove the deletion barrier, we'll have to work out
  15.  
    // a new way to handle the profile logging.
  16.  
    if slot1 := uintptr(unsafe.Pointer(slot)); slot1 >= minPhysPageSize {
  17.  
    if optr := *slot; optr != 0 {
  18.  
    // 标记旧指针
  19.  
    shade(optr)
  20.  
    }
  21.  
    }
  22.  
    // TODO: Make this conditional on the caller's stack color.
  23.  
    if ptr != 0 && inheap(ptr) {
  24.  
    // 标记新指针
  25.  
    shade(ptr)
  26.  
    }
  27.  
    }
  28.  
    }
  29.  
     

shade函数如下:

  1.  
    // Shade the object if it isn't already.
  2.  
    // The object is not nil and known to be in the heap.
  3.  
    // Preemption must be disabled.
  4.  
    //go:nowritebarrier
  5.  
    func shade(b uintptr) {
  6.  
    if obj, hbits, span, objIndex := heapBitsForObject(b, 0, 0); obj != 0 {
  7.  
    gcw := &getg().m.p.ptr().gcw
  8.  
    // 标记一个对象存活, 并把它加到标记队列(该对象变为灰色)
  9.  
    greyobject(obj, 0, 0, hbits, span, gcw, objIndex)
  10.  
    // 如果标记了禁止本地标记队列则flush到全局标记队列
  11.  
    if gcphase == _GCmarktermination || gcBlackenPromptly {
  12.  
    // Ps aren't allowed to cache work during mark
  13.  
    // termination.
  14.  
    gcw.dispose()
  15.  
    }
  16.  
    }
  17.  
    }