返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

3.5 清理

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

默认情况下,采取后台并发模式执行清理操作。

// mgc.go

func gcSweep(mode gcMode) {
    
    // 累加代龄,重置结束标志。
    mheap_.sweepgen += 2
    mheap_.sweepdone = 0
    
    // 阻塞模式。
    if !_ConcurrentSweep || mode == gcForceBlockMode {
        
        // 循环清理所有 span。
        for sweepone() != ^uintptr(0) {
            sweep.npausesweep++            // 阻塞清理计数。
        }
        
        return
    }
    
    // 并发模式。
    if sweep.parked {
        sweep.parked = false
        ready(sweep.g, 0, true)
    }
}

bgsweep

并发模式通过 bgsweep goroutine 执行。

它在 runtime.main 创建后处于待命状态。

// proc.go

// The main goroutine.
func main() {
    gcenable()
}
// mgc.go

func gcenable() {
    go bgsweep(c)
    <-c
    
    memstats.enablegc = true    // now that runtime is initialized, GC is okay
}
// mgcsweep.go

func bgsweep(c chan int) {
    
    sweep.parked = true
    c <- 1
    
    // 休眠。等待 gcSweep 唤醒。
    goparkunlock(&sweep.lock, waitReasonGCSweepWait, traceEvGoBlock, 1)
    
    for {
        // 循环从待清理栈提取 span,执行清理操作。
        for sweepone() != ^uintptr(0) {
            sweep.nbgsweep++                  // 并发清理计数。
            Gosched()
        }
        
        // 休眠,等待下次唤醒。
        goparkunlock(&sweep.lock, waitReasonGCSweepWait, traceEvGoBlock, 1)
    }
}

sweepgen

在 heap 里,专门存储清理代。

// mheap.go

type mheap struct {
    sweepgen   uint32      // sweep generation, see comment in mspan
    sweepdone  uint32      // all spans are swept
}
// sweep generation:
// if sweepgen == h->sweepgen - 2, the span needs sweeping
// if sweepgen == h->sweepgen - 1, the span is currently being swept
// if sweepgen == h->sweepgen,     the span is swept and ready to use
// if sweepgen == h->sweepgen + 1, the span was cached before sweep began and is still cached, and needs sweeping
// if sweepgen == h->sweepgen + 3, the span was swept and then cached and is still cached
// h->sweepgen is incremented by 2 after every GC

从 heap 获取内存的 allocSpan 方法里,会设置当前代龄。

// mheap.go

func (h *mheap) allocSpan(npages uintptr, manual bool, spanclass spanClass, sysStat *uint64) (s *mspan) {

    ...
    
HaveSpan:
    
     atomic.Store(&s.sweepgen, h.sweepgen)
     s.state.set(mSpanInUse)
}

sweepone

从待清理栈取出一个 span,执行清理操作。

当 mcache 需要扩充内存时,先调用 mcentral.cacheSpan,其次才是 central.grow -> mheap.allocSpan。

用 allocSpan 从堆获取内存时,并不会将 span 添加到 mcentral.full/partial 管理链表。

直到垃圾回收,才调用 mcentral.uncacheSpan 和 mspan.sweep 将其添加到管理链表。

另外,mcache 调用 mcentral.cacheSpan 扩容时,尝试主动清理。

加上 markroot,调用 uncacheSpan 收回 mcahce 所持有 span,所以再次获取必然是已经清理过的。

// mgcsweep.go

// sweeps one span
// returns number of pages returned to heap, or ^uintptr(0) if there is nothing to sweep

func sweepone() uintptr {
    
    var s *mspan
    sg := mheap_.sweepgen
    
    // 查找一个待清理 span。
    for {
        
        // 从待清理栈里提取 span,执行清理操作。
        s = mheap_.nextSpanForSweep()
        
        if s == nil {
            atomic.Store(&mheap_.sweepdone, 1)   // 结束标志。
            break
        }
        
        // 跳过闲置的 span。
        if state := s.state.get(); s.state != mSpanInUse {
            continue
        }
        
        // 找到目标,调整代龄。
        if s.sweepgen == sg-2 && atomic.Cas(&s.sweepgen, sg-2, sg-1) {
            break
        }
    }
    
    // 清理找到的 span。
    npages := ^uintptr(0)
    if s != nil {
        npages = s.npages
        
        // 参考《内存分配 - 回收》。
        s.sweep(false)
    }
    
    return npages
}
// mgcsweep.go

// nextSpanForSweep finds and pops the next span for sweeping from the
// central sweep buffers. It returns ownership of the span to the caller.
// Returns nil if no such span exists.

func (h *mheap) nextSpanForSweep() *mspan {
	sg := h.sweepgen
    
    // 循环检查所有 central。
	for sc := sweep.centralIndex.load(); sc < numSweepClasses; sc++ {
		spc, full := sc.split()
		c := &h.central[spc].mcentral
        
        // 分别检查 full 和 partail 未清理列表。
		var s *mspan
		if full {
			s = c.fullUnswept(sg).pop()
		} else {
			s = c.partialUnswept(sg).pop()
		}
        
		if s != nil {
			// Write down that we found something so future sweepers
			// can start from here.
			sweep.centralIndex.update(sc)
			return s
		}
	}
    
	// Write down that we found nothing.
	sweep.centralIndex.update(sweepClassDone)
	return nil
}

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

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

发布评论

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