本文介绍GC的源码分析(源码基于v1.14)
一、GC概述
GC会扫描哪些地方存有指针,首先变量要么分配到栈中,要么分配在在堆中。我们在之前的Go语言内存管理章节中学习到了堆对应的bitmap每2bit会指出arena哪些地址存储了对象,对象是否包含指针;还有我们的mcentral中,也会分为包含指针的span(noscan),不包含指针的span,这样的分类会减少标记的时间,提升标记的效率。对于栈来说,栈空间的指针信息都存储在函数中,使用1 bit表示一个指针大小的内存 (位于stackmap.bytedata)
Go的GC是并行GC, 也就是GC的大部分处理和普通的go代码是同时运行的, 这让GO的GC流程比较复杂。
首先GC有四个阶段, 它们分别是
- Sweep Termination: 对未清扫的span进行清扫, 只有上一轮的GC的清扫工作完成才可以开始新一轮的GC
- Mark: 扫描所有根对象, 和根对象可以到达的所有对象, 标记它们不被回收
- Mark Termination: 完成标记工作, 重新扫描部分根对象(要求STW)
- Sweep: 按标记结果清扫span
GC在满足一定条件后会被触发, 触发条件有以下几种:
- gcTriggerHeap: 当前分配的内存达到一定值就触发GC(自动)
- gcTriggerTime: 当一定时间没有执行过GC就触发GC(自动)
- gcTriggerCycle: 要求启动新一轮的GC, 已启动则跳过, 手动触发GC的
runtime.GC()
会使用这个条件(主动)
1 | type gcTriggerKind int |
二、源码分析
参考这位大神的,本人感觉应该是全网最全的,大家完全可以对照源码进行学习~~GC的实现原理
在此基础上我摘出几点并做补充:
1、STW都做了什么
目前整个GC流程会进行两次STW(Stop The World), 第一次是Mark阶段的开始, 第二次是Mark Termination阶段.
第一次STW会准备根对象的扫描, 启动写屏障(Write Barrier)和辅助GC(mutator assist).
第二次STW会重新扫描部分根对象, 禁用写屏障(Write Barrier)和辅助GC(mutator assist).
需要注意的是, 不是所有根对象的扫描都需要STW, 例如扫描栈上的对象只需要停止拥有该栈的G.
从go 1.9开始, 写屏障的实现使用了混合写屏障(Hybrid Write Barrier), 大幅减少了第二次STW的时间.
1 | // 由g0执行 |
2、辅助GC的作用
辅助GC(mutator assist):为了防止heap增速太快, 在GC执行的过程中如果同时运行的G分配了内存, 那么这个G会被要求辅助GC做一部分的工作.
在GC的过程中同时运行的G称为”mutator”, “mutator assist”机制就是G辅助GC做一部分工作的机制
辅助GC做的工作有两种类型, 一种是标记(Mark), 另一种是清扫(Sweep).
3、根对象
在GC的标记阶段首先需要标记的就是”根对象”, 从根对象开始可到达的所有对象都会被认为是存活的.
根对象包含了全局变量, 各个G的栈上的变量等, GC会先扫描根对象然后再扫描根对象可到达的所有对象.
1 | func gcMarkRootPrepare() { |
4、三色标记法在代码中怎么体现的
我们了解到,对于v1.9以后GC开始使用三色标记+混合屏障
在Go内部对象并没有保存颜色的属性, 三色只是对它们的状态的描述,
白色的对象在它所在的span的gcmarkBits中对应的bit为0,
灰色的对象在它所在的span的gcmarkBits中对应的bit为1, 并且对象在标记队列中,
黑色的对象在它所在的span的gcmarkBits中对应的bit为1, 并且对象已经从标记队列中取出并处理.
gc完成后, gcmarkBits会移动到allocBits然后重新分配一个全部为0的bitmap, 这样黑色的对象就变为了白色.
5、清除对象是干了什么
1 | // 清扫单个span |
引用: