实现原理
垃圾收集的多个阶段:
清理准备阶段(STW)
- 暂停程序,所有的处理器在这时会进入安全点
我的理解是这里stw,等待所有协程都知道要开始打开写屏障了,不然无法做到统一
- 如果当前垃圾收集循环是强制触发的,我们还需要处理还未被清理的内存管理单元
标记阶段-并发执行
-
将状态切换至
_GCmark
-
开启写屏障
-
并发标记
-
标记辅助(下面介绍)
-
在这期间遵守混合写屏障的机制
标记终止阶段 -STW
- 暂停程序、将状态切换至
_GCmarktermination
并关闭辅助标记的用户程序;通知其他goroutine关闭写屏障,停止写屏障 - 清理处理器上的线程缓存
清理阶段-并发执行
- 将状态切换至
_GCoff
开始清理阶段,初始化清理状态并关闭写屏障; - 恢复用户程序,这时候新创建的对象会标记为白色
- 后台并发清理所有的内存管理单元
触发时机
主动触发
runtime.GC
— 用户程序手动触发垃圾收集;
被动触发
-
默认2min没有触发过GC,则触发一次GC
-
分配内存时,若内存分配达到一定比例则触发
GC对CPU的使用率
在GC准备阶段,go语言给每个P创建一个mark worker协程(就是标记的协程),把对应的g指针存储到p中,这些mark worker创建后很快陷入休眠,等到标记阶段得到调度执行
GC默认的CPU目标使用率为25%
GC在mark worker中引入了三种不同的工作模式:
gcMarkWorkerFractionalMode
– 当垃圾收集的后台 CPU 使用率达不到预期时(默认为 25%),启动该类型的工作协程帮助垃圾收集达到利用率的目标,因为它只占用同一个 CPU 的部分资源,所以可以被调度;
gcMarkWorkerDedicatedMode
– 处理器专门负责标记对象,不会被调度器抢占
gcMarkWorkerIdleMode
– 当处理器没有可以执行的 Goroutine 时,它会运行垃圾收集的标记任务直到被抢占;
三种不同模式的工作协程会相互协同保证垃圾收集的 CPU 利用率达到期望的阈值
标记辅助
用户程序有可能在GC期间分配新的内存,为了保证用户程序分配内存的速度不会超出后台任务的标记速度,运行引入了标记辅助技术。
在目前Go实现中,当GC触发后,首先进入并发标记阶段,并发标记会设置一个标志gcAssistBytes
字段,这个字段存储了当前协程辅助标记的对象字节数。在并发标记阶段期间,当 Goroutine 分配新对象时,会检查此时 是否处于入不敷出的状态。
如果是,会暂停分配内存过快的哪些goroutine,并将其转去执行一些辅助标记的工作,从而达到放缓继续分配、辅助GC的标记工作的目的