浅析Go-GC
一、简介
GC 即垃圾回收,主要针对于分配在堆上的内存(栈上内存与函数的执行周期绑定,无需管理),当这些内存不再使用的时候,Go 会将其自动回收。Go 的 GC 思想采用最简单的“标记清除”法,采用广度优先搜索算法,从根节点集合出发,进行可达性分析,标记有用的对象。
二、关键技术
三色标记法
过程
首先将对象分为三个部分:黑色、灰色和白色
白色:未被标记的对象集合,可能是垃圾
灰色:已被标记但子对象未被标记的对象集合
黑色:已被标记且子对象也被标记的对象集合,不是垃圾
- 第一步:开始的时候将所有的对象都划分到白色集合中
- 第二步:从根节点(Root)出发,遍历根节点直接可达的所有对象,划分到灰色集合
- 第三步:遍历灰色集合中的所有对象,找到直接可达的白色对象,放入灰色集合中。再把原来的灰色集合中的对象放入黑色集合中
- 第四步:重复第三步,直到没有与灰色对象直接相关的白色对象,所有的对象均被划分到白色或者黑色集合
- 最后一步:清除白色集合中的所有对象
写屏障(Write Barrier)
- 写屏障是一种机制,用于在并发标记过程中捕获对对象的修改。
- 当用户程序修改对象时,写屏障会将被修改的对象标记为灰色,确保其不会被遗漏。
混合写屏障(Hybrid Write Barrier)
- Go 1.8 引入了混合写屏障,进一步优化了 GC 的性能。
- 混合写屏障结合了插入写屏障和删除写屏障的优点,减少了 STW 时间。
分代假设优化
- 虽然 Go 的 GC 不是严格的分代 GC(例如Java是分代GC),但它通过写屏障和其他优化策略,减少了对年轻对象的扫描开销。
三、触发条件
Go 的 GC 是自动触发的,主要依据以下条件:
- 堆内存增长:当堆内存达到上次 GC 后的两倍的时候
- 手动触发:手动调用 runtime.GC()可强制触发 GC
- 内存不足:当系统内存不足的时候会触发 GC
四、具体流程
大致可以分为五个阶段:准备(STW)->并发标记->标记终止(STW)->清除->完成
(1)标记准备阶段(Mark Setup)
触发条件:
- 当堆内存达到一定阈值的时候
- 手动调用 runtime.GC()
STW:
- 暂停所有 Goroutine(STW),准备开始 GC
- 执行一些初始化操作,如重置标记状态
(2)并发标记(Concurrent Marking)
- 使用三色标记法标记对象,将对象最终划分为两个区
- 此过程与用户程序并发执行
(3)标记终止(Mark Termination)
再次 STW,确保所有的对象都被正确地标记
(4)清除(Sweeping)
- 并发清除:清除白色区域内的所有对象,此过程与用户程序并发执行
- 内存回收:将回收的内存重新放入空闲内存列表,供后续使用
(5)完成阶段(Completion)
- 恢复所有的 Goroutine 的执行
- 统计 GC 结果信息
- 调整 GC 参数,如 GOGC
五、STW
在 Go 的整个 GC 过程中,存在 STW 的阶段一共有 2 个,分别是标记准备阶段和标记终止阶段
标记准备阶段
触发时机
- gc 触发的时候(例如堆内存达到阈值或者手动调用 runtime.GC()),会进入标记准备阶段
STW 的原因
- 需要暂停所有的 Goroutine,确保根对象(全局变量、栈上的变量、寄存器中的变量等)状态一致
- 如果 Goroutine 继续执行的话,可能会修改根对象,使得状态不一致
STW 的任务
- 初始化 GC 对象
- 标记根对象,将其划入灰色集合
标记终止阶段
触发时机
- 并发标记阶段完成之后,进入标记终止阶段
STW 的原因
- 需要暂停所有的 Goroutine,确保所有的对象都被正确地标记
STW 的任务
- 检查是否有遗漏的对象,并完成标记过程
浅析Go-GC
https://wuwanhao.github.io/2025/05/18/浅析Go-GC/