go 垃圾回收源码分析

关于go的gc机制,我在公司的wiki中写了,有时间会贴出来的。
大体而言,go的垃圾回收是基于标记清除算法,其实现了三色标记以及内存屏障技术来实现并发的标记清除。
其主要阶段包括:
1.标记前的准备工作,包括清理上一次gc留下来的一些object,这是stw的
2.标记阶段,即对对象进行标记,这是借助于mspan中的sweepgen来确定该对象是否需要gc
3.标记结束,重新扫描部分根对象,STW
4.按标记结果进行清扫
关于标记清除后是否会进行整理,我个人理解是会的,因为在mheap中包含了所有的msapn形成了empty和noempty列表,应该会将清除后的mspan纳入到noempty,这里的empty和noempty应该指的是是否有空闲内存。

go的垃圾回收主要借助了两个技术,一个是三色标记,一个是内存屏障。
三色标记是指,使用白色,灰色和黑色来对对象进行标记,主要分为以下步骤:
1.首先将全部的对象标记成白色
2.将直接联系对象标记成灰色
3.将灰色对象的直接联系对象变成灰色,而将原来的灰色对象变成黑色
4.不断重复2,3过程
剩下的白色对象就是垃圾

但是相对应的,三色标记在与用户程序同时进行的过程中会有问题,如当gc扫描过对象A后,开始扫描对象B,并且对象B引用对象C,但这时,对象A对对象C建立了引用,对象B对对象C删除了引用,那么对象C会被当成垃圾而回收。内存屏障便是为了解决这种问题。内存屏障主要分为读屏障和写屏障,而由于读屏障需要在程序中额外添加代码,所以基本不用;写屏障类似于钩子函数,当在对对象进行写操作的时候,便会进行相应的操作,写屏障又分为插入写和删除写,具体就不再介绍。

介绍一下gc的源码:
1.gcstrat
位于mgc.go,1206行

func gcStart(trigger gcTrigger) {
	//mp类似于一个锁,判断当前g是否可以抢占
	mp := acquirem()
	if gp := getg(); gp == mp.g0 || mp.locks > 1 || mp.preemptoff != "" {
		releasem(mp)
		return
	}
	releasem(mp)
	mp = nil

	//清扫残留垃圾,STW
	for trigger.test() && sweepone() != ^uintptr(0) {
		sweep.nbgsweep++
	}

	// Perform GC initialization and the sweep termination
	// transition.
	semacquire(&work.startSema)
	// Re-check transition condition under transition lock.
	if !trigger.test() {
		semrelease(&work.startSema)
		return
	}

	// 判断是否被强制执行
	work.userForced = trigger.kind == gcTriggerCycle
	//设置gc mode,应该类似于mspan中
	mode := gcBackgroundMode
	if debug.gcstoptheworld == 1 {
		mode = gcForceMode
	} else if debug.gcstoptheworld == 2 {
		mode = gcForceBlockMode
	}

	// 开始做,获得当前状态
	semacquire(&worldsema)

	if trace.enabled {
		traceGCStart()
	}

	// Check that all Ps have finished deferred mcache flushes.
	for _, p := range allp {
		if fg := atomic.Load(&p.mcache.flushGen); fg != mheap_.sweepgen {
			println("runtime: p", p.id, "flushGen", fg, "!= sweepgen", mheap_.sweepgen)
			throw("p mcache not flushed")
		}
	}
	//启动后台标记任务
	gcBgMarkStartWorkers()
	//重置gc,标记相关状态
	systemstack(gcResetMarkState)

	work.stwprocs, work.maxprocs = gomaxprocs, gomaxprocs
	if work.stwprocs > ncpu {
		// This is used to compute CPU time of the STW phases,
		// so it can't be more than ncpu, even if GOMAXPROCS is.
		work.stwprocs = ncpu
	}
	work.heap0 = atomic.Load64(&memstats.heap_live)
	work.pauseNS = 0
	work.mode = mode

	now := nanotime()
	work.tSweepTerm = now
	work.pauseStart = now
	if trace.enabled {
		traceGCSTWStart(1)
	}
	//正式开始,STW
	systemstack(stopTheWorldWithSema)
	// Finish sweep before we start concurrent scan.
	systemstack(func() {
		finishsweep_m()
	})
	// 清理 sync.pool sched.sudogcache、sched.deferpool
	clearpools()

	work.cycles++

	gcController.startCycle()
	work.heapGoal = memstats.next_gc

	// In STW mode, disable scheduling of user Gs. This may also
	// disable scheduling of this goroutine, so it may block as
	// soon as we start the world again.
	if mode != gcBackgroundMode {
		schedEnableUser(false)
	}
	//设置gc状态为gcmark
	setGCPhase(_GCmark)
	 // 更新 bgmark 的状态
	gcBgMarkPrepare()
	// 计算并排队root 扫描任务,并初始化相关扫描任务状态
	gcMarkRootPrepare()
	// 标记 tiny 对象
	gcMarkTinyAllocs()

	// 设置 gcBlackenEnabled 为 1,启用写屏障
	atomic.Store(&gcBlackenEnabled, 1)

	// Assists and workers can start the moment we start
	// the world.
	gcController.markStartTime = now

	// Concurrent mark.
	systemstack(func() {
		now = startTheWorldWithSema(trace.enabled)
		work.pauseNS += now - work.pauseStart
		work.tMark = now
	})
	// In STW mode, we could block the instant systemstack
	// returns, so don't do anything important here. Make sure we
	// block rather than returning to user code.
	if mode != gcBackgroundMode {
		Gosched()
	}

	semrelease(&work.startSema)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值