返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

3.4.2 标记

发布于 2024-10-12 19:16:04 字数 6975 浏览 0 评论 0 收藏 0

并发模式下,触发回收的用户 goroutine 在启动回收周期后,继续执行用户代码。

阻塞模式下,用户 goroutine 被暂停(gosched),转而参与垃圾回收执行。

gcDrain

每个 P 都有本地缓存,另有全局队列进行平衡。

有关缓存队列,请参考《其他 - 缓存队列》。

// runtime2.go

type p struct {
    gcw gcWork
}
// mgc.go

var work struct {
    full  lfstack
}

标记工作从根对象开始,然后才处理灰色队列。

// mgc.go

var work struct {
    markrootNext uint32   // next markroot job
    markrootJobs uint32   // number of markroot jobs
}
// mgcmark.go

func gcDrain(gcw *gcWork, flags gcDrainFlags) {
    
    // 根对象。
    if work.markrootNext < work.markrootJobs {
        for !(gp.preempt && (preemptible || atomic.Load(&sched.gcwaiting) != 0)) {
            
            job := atomic.Xadd(&work.markrootNext, +1) - 1
            if job >= work.markrootJobs {
                break
            }
            
            markroot(gcw, job)
            
            // 检查是否要处理其他任务(小时工、临时工)。
            if check != nil && check() {
                goto done
            }
        }
    }
    
    // 灰色队列。
    for !(gp.preempt && (preemptible || atomic.Load(&sched.gcwaiting) != 0)) {
        
        // 如果全局队列已空,则从本地转移一批过去。
        if work.full == 0 {
            gcw.balance()
        }
        
        // 提取一个对象。
        b := gcw.tryGetFast()
        if b == 0 {
            b = gcw.tryGet()   // 本地、全局。
        }
        if b == 0 {
            break
        }
        
        // 扫描该对象。
        scanobject(b, gcw)
    }
    
done:
}

markroot

统计所有根对象数量,然后按序处理。

// mgcmark.go

func markroot(gcw *gcWork, i uint32) {
    baseFlushCache := uint32(fixedRootCount)
    baseData       := baseFlushCache + uint32(work.nFlushCacheRoots)
    baseBSS        := baseData + uint32(work.nDataRoots)
    baseSpans      := baseBSS + uint32(work.nBSSRoots)
    baseStacks     := baseSpans + uint32(work.nSpanRoots)
    end            := baseStacks + uint32(work.nStackRoots)
    
    switch {
    case baseFlushCache <= i && i < baseData: flushmcache(int(i - baseFlushCache))
    case baseData <= i && i < baseBSS       : ...
    case baseBSS <= i && i < baseSpans      : ...
    case i == fixedRootFinalizers           : ...
    case i == fixedRootFreeGStacks          : ...
    case baseSpans <= i && i < baseStacks   : ...
    default:
        
        // 扫描 G.stack ...
        gp = allgs[i-baseStacks]
        scanstack(gp, gcw)
    }
}

根对象扫描标记(包括 markrootBlock、scanstack)最终调用 scanblock 函数。

根对象有自己的独立位图。

// mgcmark.go

// scanblock scans b as scanobject would, but using an explicit
// pointer bitmap instead of the heap bitmap.
// This is used to scan non-heap roots, so it does not update
// gcw.bytesMarked or gcw.scanWork.

func scanblock(b0, n0 uintptr, ptrmask *uint8, gcw *gcWork, stk *stackScanState) {
    b := b0
    n := n0
    
    for i := uintptr(0); i < n; {
        
        // 读取标记位。
        bits := uint32(*addb(ptrmask, i/(sys.PtrSize*8)))
        if bits == 0 {
            i += sys.PtrSize * 8
            continue
        }
        
        for j := 0; j < 8 && i < n; j++ {
            
            // 如果标记为 1,表示指针。
            if bits & 1 != 0 {
                // 利用指针获取目标,并将其放入本地队列。
                p := *(*uintptr)(unsafe.Pointer(b + i))
                if p != 0 {
                    if obj, span, objIndex := findObject(p, b, i); obj != 0 {
                        greyobject(obj, b, i, span, gcw, objIndex)
                    }
                }
            }
            
            // 右移,处理下一个标记位。
            bits >>= 1
            i += sys.PtrSize
        }
    }
}

scanobject

扫描对象内容,将指针字段所引用对象标记为灰色,放入本地队列。

为对象分配内存时,会在 heapArena.bitmap 记录指针信息。

// malloc.go

func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
    
    noscan := typ == nil || typ.ptrdata == 0
    
    // 如果包含指针,则需要在 bitmap 内添加记录。
    if !noscan {
        heapBitsSetType(uintptr(x), size, dataSize, typ)
    }
    
    // 垃圾回收期间,新建对象直接标记为黑色。
    if gcphase != _GCoff {
        gcmarknewobject(span, uintptr(x), size, scanSize)
    }
}

如果对象包含指针,那么会按其长度进行对齐。

比对 bitmap 记录,找到指针字段,将所引用对象加入灰色队列。

// mgcmark.go

// scanobject scans the object starting at b, adding pointers to gcw.

func scanobject(b uintptr, gcw *gcWork) {
    
    // 从 heapArena 获取 bitmap 和 span。
    hbits := heapBitsForAddr(b)
    s := spanOfUnchecked(b)
    n := s.elemsize                        // span 内 object 大小。
    
    // 大于 128KB 的大对象,分割扫描。
    if n > maxObletBytes {
        
        // 如果是从起始地址开始,那么表示尚未分隔。
        if b == s.base() {
            
            // 如果是 noscan,表示不包含指针,直接黑色即可。
            if s.spanclass.noscan() {
                gcw.bytesMarked += uint64(n)
                return
            }
            
            // 以 128KB 为单位,分割成多个 oblet,放入本地队列。
            for oblet := b + maxObletBytes; oblet < s.base()+s.elemsize; oblet += maxObletBytes {
                if !gcw.putFast(oblet) {
                    gcw.put(oblet)
                }
            }
        }
        
        // 分割后,确定对象长度。
        n = s.base() + s.elemsize - b
        if n > maxObletBytes {
            n = maxObletBytes
        }
    }
    
    // 按指针长度遍历对象内容(sizeclass 1 = 8)。
    // 如果是包含指针的复合结构,那么必然按指针长度对齐。
    // 配合标记位进行检查。
    for i = 0; i < n; i += sys.PtrSize {
        
        bits := hbits.bits()
        if i != 1 * sys.PtrSize && bits & bitScan == 0 {
            break                                           // no more pointers in this object
        }
        if bits & bitPointer == 0 {
            continue                                        // not a pointer
        }
        
        // 如果是指针字段,访问目标对象。
        obj := *(*uintptr)(unsafe.Pointer(b + i))
        if obj != 0 && obj - b >= n {
            // 将目标灰色对象放入队列。
            if obj, span, objIndex := findObject(obj, b, i); obj != 0 {
                greyobject(obj, b, i, span, gcw, objIndex)
            }
        }
    }
    
    gcw.bytesMarked += uint64(n)
    gcw.scanWork += int64(i)
}

greyobject

灰色对象(存活)需要在 span.gcMarkBits 标记。

非 debug 模式,greyobject 会将标记结果保存在 gcMarkBits 里。

然后在 mspan.sweep 时,将 gcMarkBits 复制为 allocBits。

// mgcmark.go

func greyobject(obj, base, off uintptr, span *mspan, gcw *gcWork, objIndex uintptr) {
    
    // 从 span.gcmarkBits 获取标记。
    mbits := span.markBitsForIndex(objIndex)
    
    // 避免重复标记。
    if mbits.isMarked() {
        return
    }
    
    // 设置标记。
    mbits.setMarked()
    
    // 如不包含指针,直接为黑色,无须放入队列。
    if span.spanclass.noscan() {
        gcw.bytesMarked += uint64(span.elemsize)
        return
    }
    
    // 放入本地队列。
    if !gcw.putFast(obj) {
        gcw.put(obj)
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文