Golang 垃圾回收
Go 垃圾回收使用的是标记-回收算法,分为四个阶段:标记准备阶段,标记阶段,标记终止阶段,清理阶段。
一、并行模式的垃圾回收流程
1)标记准备阶段
进行的操作:
- 判断当前协程是否可以抢占,不可以抢占不触发GC;
- 启动后台标记协程;
- 暂停用户协程(STW - stop the word);
- 开启写屏障;
- 将根对象入队;
??? 标记协程的启动数量?
每个逻辑处理器P会启动一个标记协程,但并不是每个标记协程都会执行,同时执行的标记协程数量大约是P的25%,也就是有25%的CPU用在GC上。例如,有4个P,那同一时间只有1个标记协程在执行。这样做可以减少GC给用户程序带来的影响。
[代码:runtime/mgc.go.startCycle()]
??? 标记协程的调度过程?
STW后,重新启动所有协程,调度器会进行新的一轮调度,这时会判断程序是否处于GC阶段,如果是则判断是否需要执行标记任务。怎么判断呢?分两种情况:
第一种,需要执行的标记协程数(dedicatedMarkWorkersNeeded)大于0时,此时是DedicatedMode模式;
第二种,fractionalUtilizationGoal大于0且当前P执行标记任务的时间小于fractionalUtilizationGoal*当前标记周期的时间,这种情况只会占用部分时间来进行标记任务,超出时间后,其它协程可以抢占,此时是FractionalMode模式。
[代码:runtime/mgc.go.findRunnableGCWorker()]
??? 为什么要开启写屏障?
为了维护三色不变性,用户协程和标记协程是并行执行的,在标记的过程中,函数栈内可能会有新分配的对象,如果没有开启写屏障,那新分配的对象是标记为白色的,可能会在这次GC中被误回收。写屏障的作用就是把新分配的对象标记为灰色,避免误回收,这些灰色对象会在下次GC中判断是否需要回收。写屏障可以大大减少STW的时间。
??? 强三色性不变性和弱三色性不变性?
强三色性:所有的白色对象都不能被黑色对象引用
弱三色性:允许白色对象被黑色对象引用,但白色对象必须有一条路径是被灰色对象引用的
??? 根对象包含什么?
根对象包含了全局对象、栈对象(goroutine栈)
2)标记阶段
进行的操作:
- 恢复用户协程;
- 使用三色标记法开始标记,此时用户协程和标记协程并行执行;
??? 标记任务的模式有哪些?
1、DedicatedMode:代表当前P专门负责标记对象,不会被抢占;
2、FractionalMode:在标记任务到达目标时间后,会自动退出;
3、IdleMode:当前P没有可以执行的G,执行标记任务,直到被抢占;
??? 在DedicateMode模式中,协程不能被抢占,那这个P中的G将得不到执行,怎么办呢?
第一次执行的标记协程是可以被抢占,如果标记协程已经被抢占了,则将当前P中的G挪到全局队列中,进入不能被抢占的模式
三色标记法:
黑色对象:对象已被标记,包含的子对象也被标记,不会在本次GC中回收
灰色对象:对象已被标记,但包含的子对象尚未标记,不会在本次GC中回收
白色对象:对象未标记,会在本次GC中回收
三色标记法用是否可达来判断对象是否存活,首先会扫描根对象,将根对象引用的对象标记为灰色;然后处理灰色对象,将灰色对象引用的对象标记为灰色,自身标记为黑色,循环处理灰色对象,直到灰色对象集合为空,此时只剩黑色对象和白色对象
3)标记终止阶段
进行的操作:
- 暂停用户协程
- 计算下一次触发GC时需要达到的堆目标
- 唤醒后台清扫协程
[代码:runtime/mgc.go.gcMarkTermination()]
??? 这次暂停用户协程(STW)的原因?
这次STW会重新扫描部分根对象,确保所有可达对象都已标记,保证垃圾回收的准确性。
4)清理阶段
进行的操作:
- 关闭写屏障
- 恢复用户协程
- 异步清理回收
??? 垃圾清扫过程?
垃圾清扫采用的是懒清扫策略,即执行少量清扫工作后就会让渡自己的执行权力,不需要一直执行。因此在触发下一轮GC时,可能会存在尚未被清理的内存,需要先将他们清扫完成。
??? 那如果未被清扫的内存太多,会拖后下次GC开始的时间,怎么办呢?
Go使用了辅助清扫的手段,在用户协程分配内存的时候,判断是否需要清扫。分配内存指的是向mcentrel申请内存或者大对象内存分配时。
??? 清扫协程有多少个?
程序中只有一个清扫协程,在清扫阶段与用户协程同时进行。
二、垃圾回收触发时机
- 内存分配量达到阈值:每次内存分配都会判断当前内存是否达到阈值,如果是则触发GC。阈值为当前堆内存达到2倍上一次GC后的内存,2倍为内存增长率,可通过环节变量GOGC调整;
- 定时触发:默认2分钟触发一次,这个配置在runtime/proc.go里的forcegcperiod参数;
- 手动触发:使用runtime.GC() 手动触发;
三、辅助标记
??? 解决的问题是?
当用户协程分配内存的速度大于标记协程扫描的速度时,GC标记阶段不会结束,无法完成完整的GC周期,造成内存泄露。
??? 辅助标记干了什么?
辅助标记在垃圾回收标记的阶段进行,当用户程序分配内存的时候,先进行指定的扫描任务,即分配了多少内存就要完成多少标记任务
浅析 Golang 垃圾回收机制
深入理解Go-垃圾回收机制
Go语言垃圾收集器
《Go 语言底层原理剖析》 - 郑建勋