gc入口
- 手动出发:
runtime.GC
- 内存使用达到某个阈值:
runtime.mallocgc
- 后台有个goroutine每2min触发一次:
forcegchelper
三色
黑色:已经扫描完毕,子节点扫描完毕。(gcmarckbits=1,且在队列外)
灰色:已经扫描完毕,子节点未扫描完毕。(gcmarckbits=1,且在队列内)
白色: 未扫描,collector不知道任何相关信息
三色主要是为了能让垃圾回收流程
与应用流程
并发执行
GC roots
- 全局变量
- goroutine栈
GC发展史
v1.3-标记清除法
主要分为两个步骤:
- 启动STW
- mark标记
- 停止STW
- sweep清除
- 开启STW,停止程序运行,从root节点开始标记
2. 从root出发,标记所有可达对象
3. 停止STW,回收所有未被标记的对象
缺点
:整个GC期间需要STW,性能太差
v.15 三色标记法
三色标记法如果仅仅是这样的话,仍然需要STW
,但是为什么呢?
举一个例子:
本该存在的对象被垃圾回收掉,这可是bug!
这时你肯定会疑惑,这三色标记法有问题,加了STW影响性能,不加STW有bug,这也用不了啊。
那我们继续看go是如何解决这个问题的
在三色标记过程中,对象如果要缺失,要同时满足以下两点:
- 白色对象被黑色对象引用
- 灰色对象与白色对象间的关系遭到破坏
只要破环上面两个条件之一,就能保证对象不会丢失,所以golang团队提出了两种破环条件的方式:
- 强三色不变式
- 弱三色不变式
强三色不变式
不允许黑色对象引用白色对象
弱三色不变式
所有被黑色对象引用的白色对象,都有一条路径能够被灰色对象引用
为次,提出了两种实现机制:
- 插入写屏障
- 删除写屏障
插入写屏障
当A对象引用B对象时,将B标记为灰色
注意:插入写屏障只在堆中生效,不在站空间生效,因为go可能有很多gotoutine,都进行栈的写屏障保护会有性能问题
下面演示插入写屏障如何工作:
由于堆区的插入写屏障机制,obj3得到保护,被标记为灰色。但是因为栈上没有插入写屏障机制,所以在扫描完成时,仍然可能存在栈上白色对象被黑色对象引用,所以最后需要在栈空间上STW,再扫描一次
所以插入写屏障,是在正常三色标记后,需要在栈上STW,并再扫描一次
删除写屏障
当删除引用时,被删除的对象是灰色或者白色,则被标记成灰色
下面演示删除写屏障如何工作:
此时我们发现,在栈上删除对象会触发删除写屏障机制,因为删除写屏障会在GC开始时,对整个栈做快照,从而达到拦截删除操作 但是删除写屏障有一个缺点,如下所示:
一个对象被删除后,虽然可能没有其他存活对象引用它,但是仍然在本轮GC中存活,会降低回收精度,增加下轮GC成本
总结
插入写屏障和删除写屏障都解决了单一三色标记法需要长时间STW的问题,并且goV1.5采用了插入写屏障+三色标记法作为golang的GC机制。
两种写屏障的对比
:
- 插入写屏障:
- 栈上的操作无法拦截,最后需要对栈STW,再重新扫描一次
- 删除写屏障:
- GC开始时,会记录整个栈的快照,用来拦截操作
- 回收精度低
V1.8混合写屏障机制
在gov1.8将两个机制的优点采纳,提出了混合写屏障机制:
- GC刚开始的时候,将栈上所有可达对象全部标记为黑色
- GC期间,任何在栈上创建的对象,均为黑色
- 堆上被删除的对象标记为灰色
- 堆上新增的对象被标记为灰色
我们来分析下过程:
场景1: 堆对象被删除引用,并被栈对象引用
场景2: 栈对象被删除引用,并被另一个栈对象引用
场景3: 堆对象被删除引用,并被另一个堆对象引用
场景4: 栈对象被删除引用,并被另一个堆对象引用
总结
混合写屏障机制:只在堆中生效,避免了最后对栈的STW以及re-scan,提升GC效率
GC标记简要过程
- 标记首先找到所有要标记的root:全局变量以及goroutine的栈
- 在标记过程中,会将表记的灰色对象放入
gcWork
中的wbuf1或wbuf2中
,一个满了再放另一个,此时是无索的放。当两个都满了,就加锁往work的全局full里面放。 - 在标记过程中,goroutine和扫描goutine并发执行,所以会将此时修改的指针的指向前/后对象放到wbBuf里面
- 接下来唤醒清扫流程
GC清扫
// gcenable is called after the bulk of the runtime initialization,
// just before we're about to start letting user code run.
// It kicks off the background sweeper goroutine, the background
// scavenger goroutine, and enables GC.
func gcenable() {
// Kick off sweeping and scavenging.
gcenable_setup = make(chan int, 2)
go bgsweep()
go bgscavenge()
<-gcenable_setup
<-gcenable_setup
gcenable_setup = nil
memstats.enablegc = true // now that runtime is initialized, GC is okay
}
bgsweep
: 负责清扫mspan,用gcmarkbits替换allocbits,把unswept的mspan变成swept的mspan
bgscavenge
: 负责奖内存归还给os