GC
三色标记法过程
- 开始前所有对象都是白色
- 将根节点指向的对象标记成灰色
- 遍历每个灰色对象,将自己指向的对象标成灰色,同时将自己标成黑色
- 重复至没有灰色对象
- 清除剩余的所有白色对象
写屏障
因为标记过程是并发的,在并发阶段新创建的对象如何处理着色?
- 白色,不行,万一引用这个新对象的是个黑色,那在这一轮标记结束后,该对象会被清除,可能会引起用户程序错误
- 灰色,可行,染成灰色是偏保守但不会出错的方案,但如果这个对象实际上不是存活对象的话,这轮gc是肯定不会清除他的,只能等下一轮gc
- 黑色,不行,万一新对象引用了一个白对象,该白对象就可能不会被扫描到,该白对象会被清除,可能会引起用户程序错误 (存疑?这么说对不对?)
完整过程
标记准备
- stw
- 所有处理器都会进入安全点
标记
- 将gc状态改为_GCmark,在堆上开启写屏障,栈上不启用,将根对象扫描任务入队(包括栈对象、全局对象以及不在堆中的运行时数据结构)
- 恢复程序执行(/usr/local/go/src/runtime/mgc.go:1381)
- 遍历根节点扫描任务队列,扫描goroutine栈时会导致goroutine停止,并将栈上的所有指针置灰(/usr/local/go/src/runtime/mgcmark.go:1485)
- 遍历灰色队列,将灰色置为黑色,并将其指向的对象置灰
- 分布式终止算法检测是否标记完毕
标记结束
- stw,将gc状态改为_GCmarktermination
- 关闭工作线程、协助线程
清除
- 将gc状态改为_GCoff,初始化清理状态并关闭写屏障(/usr/local/go/src/runtime/mgc.go:280 setGCPhase)
- 恢复程序执行
- 后台并发清理所有内存管理单元,在申请内存时才会惰性回收内存
stop the world -> 修改屏障 -> start the world
混合写屏障
特点:
- 写屏障只应用在堆上启用,栈上不启用。
- GC期间新分配的对象为黑色。
- 被删除和被添加的对象均标记为灰色。
好处:
- 降低了标记终止状态的stw时间
关于协助线程
默认gc会遵从用户设置的gc消耗cpu上限,但如果用户内存分配过快,导致gc不过来时,gc会通过抢占goroutine来主动抢占cpu使用量,选择goroutine的原则是:谁分配的快就抢占谁
触发时机
后台触发
runtime会在程序初始化时开启一个goroutine用来定时强制触发gc
用户手动触发
用户手动调用runtime.GC()
申请内存时触发
申请内存时根据堆大小触发垃圾收集