垃圾回收机制
- 基本概念
Go语言垃圾回收是一种非分代,非紧缩,写屏障,三色并发标记清理机制。
1.1 分代算法
将堆划分为两个或多个称为 代(generation) 的空间。新创建的对象存放在称为 新生代(young generation) 中(一般来说,新生代的大小会比 老年代 小很多),随着垃圾回收的重复执行,生命周期较长的对象会被 提升(promotion) 到老年代中。
1.2 紧缩算法
会尝试将对象从碎片页(包含大量小空闲内存的页)中迁移整合在一起,来释放内存。这些对象会被迁移到另外的页上,因此也可能会新分配一些页。而迁出后的碎片页就可以返还给操作系统了。
1.3 写屏障
允许用户代码跟垃圾回收同时运行,需要维护一条约束条件:黑色对象绝对不能引用白色对象
由于在三色标记算法中,黑色对象已经处理完毕,它不会被重复扫描。那么,这个对象引用的白色对象将没有机会被着色,最终会被误当作垃圾清理。
关于写屏障的用处 如下面的例子
GC前:
stack->a->b ; a为栈中申请的对象,b为堆中申请的对象,a对象中存在对b的引用;
stack->c ; c 也是栈中申请的对象。
stop the world, mark。 这里a,c都会被标记为灰色;b为白色
start the world 反复mark。
由于是并发的mark,我们假设c先被处理,c没有引用其他对象,所以直接置黑,从队列中取出;此时c为黑色,a为灰色,b为白色
假设这时用户做了如下操作:
a=nil
new(d);
c->b; 即,将a中对b的引用置为空(你也可以理解为将a中对其他任何内存对象的引用都清空),随即申请d对象,然后在c中增加对b的引用。
由于c已经是黑色,所以不会再去扫描他,那么本次内存扫描就不可能找得到b;而d对象由于刚申请出来,还没有被引用,所以这里只对a进行了mark:a:黑色,b:白色;c:黑色;d:白色
这时用户又做了:
b->d; 由于b无法被扫描到,这里显然d也不会被扫描到。 这样的状况会一直持续到这轮反复mark结束(即灰色队列为空)。
stop the world, mark termination。 sweep。 整个GC结束, b,d的内存空间都是白色,所以在sweep时会被清理掉。如何避免这种误清理呢?
写屏障的功能就是在 c->b发生时,对b标记为灰色,入队, 以及在b->d发生时,对d标记为灰色,入队,这样,在整个反复mark阶段结束时,我们能确保这段时间新发生的对白色对象的内存引用操作都被处理到(变黑),b和d就不会被误清理。写屏障在第一次扫描完,标记入队后,反复标记时开启写屏障, sweep前将写屏障关闭。
简而言之,写屏障的作用大致是: 可以确保不会有对象A直接引用白色对象B(发生时将白色对象置灰)。这里有个小细节,go 1.5中不管对象A是什么颜色,只要他引用了对象B,就将B置灰
三色标记(解释1)
这是让标记和用户代码并发的基本保障,基本原理:
起初所有对象都是白色
扫描找出所有可达对象,标记为灰色,放入待处理队列。
从队列提取灰色对象,将其引用对象标记为灰色放入队列,自身标记为黑色。
写屏障监视对象内存修改,重新标记或放回队列
三色标记(解释2)
初始状态下每个内存对象都是白色标记,
第一轮先扫描所有可达的内存对象,标记为灰色放入队列
第二轮可以恢复start the world,将第一步队列中的对象引用的对象置为灰色加入队列,一个对象引用的所有对象都置灰并加入队列后,这个对象才能置为黑色并从队列之中取出
循环往复,最后队列为空时,整个图剩下的白色内存空间即不可到达的对象,即没有被引用的对象;
第三轮再次stop the world,将第二轮过程中新增对象申请的内存进行标记(灰色),这里使用了writebarrier(写屏障)去记录这些内存的身份;
当完成全部扫描和标记工作后,剩余不是白色就是黑色,分别代表要待回收和活跃对象,
清理操作只需将白色对象内存收回即可
参考:
https://segmentfault.com/a/1190000012597428
https://www.zhihu.com/question/62000722/answer/193462425
https://www.tuicool.com/articles/z26Njq6
初始化
初始化过程非常简单,重点是设置 gcpercent 和 next_gc 阈值
func gcinit() {
//设置并发执行器
work.markfor = parforalloc(_MaxGcproc)
//设置GC百分比
_ = setGCPercent(readgogc())
//设置启动阀值(4MB)
memstats.next_gc = heapminimum
}
启动
mallocgc 函数会检查垃圾回收触发条件
func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer {
//分配黑色对象
if gcphase == _GCmarktermination || gcBlackenPromptly {
systemstack(func() {
gcmarknewobject_m(uintptr(x), size)
})
}
//垃圾回收触发条件
if shouldhelpgc && shouldtriggergc() {
//开启垃圾回收
startGC(gcBackgroundMode, false)
} else if gcBlackenEnabled != 0 {
//辅助参与回收任务
gcAssistAlloc(size, shouldhelpgc)
}else if shouldhelpgc && bggc.working != 0 {
//让出资源
gp := getg()
if gp != gp.m.g0 && gp.m.locks == 0 && gp.m.preemptoff == "" {
Gosched()
}
}
}
//heap_live 大于next_gc阀值,可以开启内存回收
func shouldtriggergc() bool {
return memstats.heap_live >= memstats.next_gc && atomicloaduint(&bggc.working) == 0
}
//开启GC工作
func startGC(mode int, forceTrigger bool) {
//bgcc结构体保存GC状态
if !bggc.started {
//创建GC
bggc.working = 1
bggc.started = true
go backgroundgc()
} else if bggc.working == 0 {
//唤醒GC
bggc.working = 1
ready(bggc.g, 0)
}
}
//GC任务
func backgroundgc() {
bggc.g = getg()
for {
gc(gcBackgroundMode)
bggc.working = 0
//休眠中,等待唤醒
goparkunlock(&bggc.lock, "Concurrent GC wait", traceEvGoBlock, 1)
}
}
func gc(mode int) {
//创建MarkWorker
if mode == gcBackgroundMode {
gcBgMarkStartWorkers()
}
//STW停止,用户活动停止
systemstack(stopTheWorldWithSema)
/*当前是STW停止状态*/
//开始并发扫描,标记模式
if mode == gcBackgroundMode {
gcController.startCycle()
systemstack(func() {
//启用写屏障
setGCPhase(_GCscan)
//允许黑色对象标记
atomicstore(&gcBlackenEnabled, 1)
//STW开启
startTheWorldWithSema()
/*当前是STW开始状态*/
//并发扫描(RootData,RootBss,RootFinalizers,RootSpans)
gcscan_m()
//记阶段开始
setGCPhase(_GCmark)
}
//等待第一轮扫描任务结束信号通知
work.bgMark1.wait()
//第二轮扫描,目标新增白色对象和剩余区段
systemstack(func() {
//再次扫描全局RootData,RootBss
markroot(nil, _RootData)
markroot(nil, _RootBss)
}
//等待第二轮扫描任务结束信号通知
work.bgMark2.wait()
//STW停止,用户活动停止
systemstack(stopTheWorldWithSema)
gcController.endCycle()
}
//禁止黑色标记操作
atomicstore(&gcBlackenEnabled, 0)
//开启写屏障
setGCPhase(_GCmarktermination)
systemstack(func() {
//完成最终标记工作
gcMark(startTime)
})
systemstack(func() {
//关闭写屏障
setGCPhase(_GCoff)
//开启清理操作
gcSweep(mode)
})
//全部工作完成,STW开启
systemstack(startTheWorldWithSema)
}
func gcBgMarkStartWorkers() {
// 给每一个P都绑定一个MarkWorker
for _, p := range &allp {
if p.gcBgMarkWorker == nil {
go gcBgMarkWorker(p)
//暂停,确保Worker绑定到P后再继续
notetsleepg(&work.bgMarkReady, -1)
noteclear(&work.bgMarkReady)
}
}
}
//GCscan,GCmark,GCmarktermination这三个状态,写屏障开启,其他状态,写屏障关闭
func setGCPhase(x uint32) {
writeBarrierEnabled = gcphase == _GCmark || gcphase == _GCmarktermination || gcphase == _GCscan
}