返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

3.3.1 触发

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

除因为内存分配触发阈值引起回收外,sysmon 和 runtime.GC 调用也可以启动。

// mgc.go

const (
    // 达到触发阈值。(mallocgc)
    gcTriggerHeap gcTriggerKind = iota
    
    // 长时间没有启动垃圾回收,强制执行。(sysmon)
    gcTriggerTime
    
    // 手工调用 GC 启动一次回收。(runtime.GC)
    gcTriggerCycle
)

mallocgc

分配内存时,并非每次都会检查。

只在大尺寸内存分配时才会进行(shouldhelpgc = true),比如扩容、大对象等。

// malloc.go

func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {

    shouldhelpgc := false
    
    if size <= maxSmallSize {
        // 小对象。
        v, span, shouldhelpgc = c.nextFree(spc)
    } else {
        // 大对象。
        shouldhelpgc = true
        span = largeAlloc(size, needzero, noscan)
    }
    
    if shouldhelpgc {
        if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
            gcStart(t)
        }
    }
    
    return x
}
// malloc.go

func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, shouldhelpgc bool) {
    shouldhelpgc = false
    
    if freeIndex == s.nelems {
        c.refill(spc)
        shouldhelpgc = true
    }
    
    return
}

启动前需要检查相关条件。

// mgc.go

func (t gcTrigger) test() bool {
    
    // 不允许启动的状态。
    if !memstats.enablegc || panicking != 0 || gcphase != _GCoff {
        return false
    }
    
    switch t.kind {
    case gcTriggerHeap:
        
        // 检查是否突破阈值。
        return memstats.heap_live >= memstats.gc_trigger
        
    case gcTriggerTime:
        
        if gcpercent < 0 {
            return false
        }
        
        // 检查时间差,确认是否长时间(2 分钟)没有启动回收。
        lastgc := int64(atomic.Load64(&memstats.last_gc_nanotime))
        return lastgc != 0 && t.now-lastgc > forcegcperiod
        
    case gcTriggerCycle:
         
        // 避免在回收进行中再次启动。
        // 调用 runtime.GC 会递增 t.n,随后回收周期启动时 work.cycles 会递增。
        // 如果相等,则表示已经进入回收标记阶段,不能再次启动。
        return int32(t.n-work.cycles) > 0
    }
    
    return true
}
// proc.go

// forcegcperiod is the maximum time in nanoseconds between garbage
// collections. If we go this long without a garbage collection, one
// is forced to run.

var forcegcperiod int64 = 2 * 60 * 1e9

runtime.GC

在完整清理周期结束前,调用该函数会等待。

1. 如果当前正在执行标记,那么等待此次(work.cycles = N)标记结束。

2. 启动 N + 1 次回收周期。 <-- 这是本次调用要启动的回收周期, work.cycles++ 在 gcStart 中。

3. 等待 N + 1 次标记结束。

4. 等待 N + 1 次清理结束。

// mgc.go

// GC runs a garbage collection and blocks the caller until the
// garbage collection is complete. It may also block the entire
// program.

func GC() {
    n := atomic.Load(&work.cycles)
    
    // 等待已启动的第 N 次循环标记结束。
    gcWaitOnMark(n)
    
    // 启动 N + 1 次循环。
    gcStart(gcTrigger{kind: gcTriggerCycle, n: n + 1})
    
    // 等待 N + 1 次标记结束。
    gcWaitOnMark(n + 1)
    
    // 等待 N + 1 次清理结束。
    for atomic.Load(&work.cycles) == n+1 && sweepone() != ^uintptr(0) {
        Gosched()
    }
}

sysmon, forcegc

如果长时间没有执行过回收,那么强制启动。

触发回收操作的是一个专门的 goroutine。它一直处于休眠循环中,等待唤醒。

从条件测试函数看,强制回收不检查阈值。

// proc.go

// start forcegc helper goroutine

func init() {
    go forcegchelper()
}
// proc.go

func forcegchelper() {
    forcegc.g = getg()
    
    for {
        // 休眠,等待 sysmon 唤醒。
        atomic.Store(&forcegc.idle, 1)
        goparkunlock(&forcegc.lock, waitReasonForceGCIdle, traceEvGoBlock, 1)    // M 停止执行该 G,且不放回队列。
        
        // 启动回收。
        gcStart(gcTrigger{kind: gcTriggerTime, now: nanotime()})
    }
}
// runtime2.go

var forcegc forcegcstate

type forcegcstate struct {
    lock mutex
    g    *g
    idle uint32
}

所谓唤醒,就是将 forcegc goroutine 重新放回任务队列。

// proc.go

func sysmon() {
    for {
        usleep(delay)
        
        // 检查是否要强制执行回收。
        if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 {
            
            // 重新放回任务队列,等待唤醒执行。
            forcegc.idle = 0
            
            var list gList
            list.push(forcegc.g)
            injectglist(&list)     // 将列表中的 G 全部放入全局待运行队列。
        }
    }
}

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

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

发布评论

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