返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

3.3.1 触发

发布于 2024-10-12 19:15:57 字数 6661 浏览 0 评论 0 收藏 0

除因为内存分配触发阈值引起回收外,还有系统监控等引发的强制回收。

// mgc.go

const (
	// gcTriggerHeap indicates that a cycle should be started when
	// the heap size reaches the trigger heap size computed by the
	// controller.
	gcTriggerHeap gcTriggerKind = iota

	// gcTriggerTime indicates that a cycle should be started when
	// it's been more than forcegcperiod nanoseconds since the
	// previous GC cycle.
	gcTriggerTime

	// gcTriggerCycle indicates that a cycle should be started if
	// we have not yet started cycle number gcTrigger.n (relative
	// to work.cycles).
	gcTriggerCycle
)

分配内存时,并非每次都检查。仅大块分配才会进行(shouldhelpgc),如扩容、大对象等。

// malloc.go

func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
    
    shouldhelpgc := false
    
    if size <= maxSmallSize {
        v, span, shouldhelpgc = c.nextFree(tinySpanClass)
    } else {
        shouldhelpgc = true
		span = c.allocLarge(size, noscan)
    }
}
// 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
	}
}

测试是否达到触发条件。

// malloc.go

func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
	if shouldhelpgc {
		if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
			gcStart(t)
		}
	}    
}
// mgc.go

// A gcTrigger is a predicate for starting a GC cycle. Specifically,
// it is an exit condition for the _GCoff phase.

type gcTrigger struct {
	kind gcTriggerKind
	now  int64          // gcTriggerTime: current time
	n    uint32         // gcTriggerCycle: cycle number to start
}

type gcTriggerKind int
// mgc.go

// test reports whether the trigger condition is satisfied, meaning
// that the exit condition for the _GCoff phase has been met. The exit
// condition should be tested when allocating.

func (t gcTrigger) test() bool {
	switch t.kind {
        
	case gcTriggerHeap:
		return gcController.heapLive >= gcController.trigger
        
	case gcTriggerTime : ...
	case gcTriggerCycle: ...     
	}
	return true
}

强制回收

强制启动垃圾回收分手动和自动两种。手动是用户直接调用 runtime.GC,自动则由 sysmon 启动。

runtime.GC

当我们调用 runtime.GC 时,可能后台正在并行执行第 N 次垃圾回收。不管 N 处于回收周期的哪个阶段,都应该先等它结束。直到 N 彻底完成后,才开始我们的 N + 1 回收周期,直到清理(sweep)结束。注意,该函数并未调用 test 检查触发条件,且会阻塞用户代码。

// 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() {
    
	// We consider a cycle to be: sweep termination, mark, mark
	// termination, and sweep. This function shouldn't return
	// until a full cycle has been completed, from beginning to
	// end. Hence, we always want to finish up the current cycle
	// and start a new one. That means:
	//
	// 1. In sweep termination, mark, or mark termination of cycle
	// N, wait until mark termination N completes and transitions
	// to sweep N.
	//
	// 2. In sweep N, help with sweep N.
	//
	// At this point we can begin a full cycle N+1.
	//
	// 3. Trigger cycle N+1 by starting sweep termination N+1.
	//
	// 4. Wait for mark termination N+1 to complete.
	//
	// 5. Help with sweep N+1 until it's done.
	//
	// This all has to be written to deal with the fact that the
	// GC may move ahead on its own. For example, when we block
	// until mark termination N, we may wake up in cycle N+2.

    
    // 等待 N 标记结束。
	n := atomic.Load(&work.cycles)
	gcWaitOnMark(n)

    // 启动 N + 1 周期。
	gcStart(gcTrigger{kind: gcTriggerCycle, n: n + 1})
    
    // 等待 N + 1 标记结束
	gcWaitOnMark(n + 1)
    
    // 执行清理任务。
	for atomic.Load(&work.cycles) == n+1 && sweepone() != ^uintptr(0) {
		sweep.nbgsweep++   // 非后台清理。
		Gosched()
	}

    // 等待清理结束。
	for atomic.Load(&work.cycles) == n+1 && !isSweepDone() {
		Gosched()
	}
}
// mgc.go

// gcWaitOnMark blocks until GC finishes the Nth mark phase. If GC has
// already completed this mark phase, it returns immediately.

func gcWaitOnMark(n uint32) {
	for {
		// Disable phase transitions.
		lock(&work.sweepWaiters.lock)
        
        // 当前周期序号。
		nMarks := atomic.Load(&work.cycles)
        
        // 已完成标记。
		if gcphase != _GCmark {
			// We've already completed this cycle's mark.
			nMarks++
		}
        
        // 当前周期超过 n,结束。
		if nMarks > n {
			// We're done.
			unlock(&work.sweepWaiters.lock)
			return
		}
        
        // gcStart: work.cycles++
        // gcMarkTermination: wake

		// Wait until sweep termination, mark, and mark
		// termination of cycle N complete.
		work.sweepWaiters.list.push(getg())
		goparkunlock(&work.sweepWaiters.lock, waitReasonWaitForGCCycle, traceEvGoBlock, 1)
	}
}

sysmon

如垃圾回收已长时间未执行,那么也会强制启动。

// proc.go

func sysmon() {
	for {
		usleep(delay)
        
        ...

		// check if we need to force a GC
		if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 {
			lock(&forcegc.lock)
			forcegc.idle = 0
            
            // 唤醒。(将 forcegc G 放回任务队列)
			var list gList
			list.push(forcegc.g)
			injectglist(&list)
            
			unlock(&forcegc.lock)
		}
	}
}
// mgc.go

func (t gcTrigger) test() bool {
	switch t.kind {
	case gcTriggerHeap: ...
        
	case gcTriggerTime:
        
        // GOGC < 0, OFF!
		if gcController.gcPercent.Load() < 0 {
			return false
		}
        
        // 超过 2 分钟。
		lastgc := int64(atomic.Load64(&memstats.last_gc_nanotime))
		return lastgc != 0 && t.now-lastgc > forcegcperiod
        
	case gcTriggerCycle: ...
	}
	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.
//
// This is a variable for testing purposes. It normally doesn't change.
var forcegcperiod int64 = 2 * 60 * 1e9

专用 forcegc G 启动并发回收,不会再次检查触发条件。

// proc.go

// start forcegc helper goroutine
func init() {
	go forcegchelper()
}
// proc.go

func forcegchelper() {
    
    // 保存到全局变量。
	forcegc.g = getg()
    
	for {
		lock(&forcegc.lock)
		if forcegc.idle != 0 {
			throw("forcegc: phase error")
		}
        
        // 休眠,等待 sysmon 唤醒。
		atomic.Store(&forcegc.idle, 1)
		goparkunlock(&forcegc.lock, waitReasonForceGCIdle, traceEvGoBlock, 1)
        
		// 启动并发回收。
		gcStart(gcTrigger{kind: gcTriggerTime, now: nanotime()})
	}
}
// runtime2.go

var forcegc forcegcstate

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

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

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

发布评论

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