1.三色标记
三色标记算法将程序中的对象分成白色、黑色和灰色三类:
- 白色对象:潜在的垃圾,其内存可能会被垃圾收集器回收;
- 黑色对象:活跃的对象,不存在任何引用外部指针的对象以及从根对象可达的对象;
- 灰色对象:活跃的对象,存在指向白色对象的外部指针,垃圾收集器会扫描这些对象的子对象;
在垃圾收集器开始工作时,程序中不存在任何的黑色对象,垃圾收集的根对象会被标记成灰色。垃圾收集器从灰色对象集合中取出对象开始扫描,将对应灰色对象标记为黑色,并将指向的对象标记成灰色。当灰色集合中不存在任何对象时,标记阶段就会结束。

若在并发或者增量的标记算法中保证正确性,需要达成以下两种三色不变性(Tri-color invariant)中的一种:
- 强三色不变性:黑色对象不会指向白色对象,只会指向灰色对象或者黑色对象;
- 弱三色不变性:黑色对象指向的白色对象必须包含一条从灰色对象经由多个白色对象的可达路径;

2.golang实现
2.1.触发时机&参数
内存条件触发:
golang运行时的默认配置是在堆内存达到上一次gc的2
倍时,触发新一轮的gc。通过环境变量GOGC
调整,默认为100
,即增长100%
的堆内存才会触发gc。
golang 1.19
引入了GOMEMLIMIT
参数,限制golang的总内存使用量,达到阈值时触发gc。该参数不是强约束,若设置过小导致gc占用CPU过高时,会优先保证gc的CPU最高占用为50%,突破此阈值。
除了上面两个参数控制触发外,也可以手动触发以及系统默认2分钟的强制触发。
2.2.标记机制
标记阶段占用25%的CPU资源。为了减少竞争,gc工作池采用生产者和消费者模型,运行时在每个P上会保存独立的待扫描工作。
混合写屏障:结合Dijkstra插入写屏障和Yuasa删除写屏障实现,将被覆盖的对象标记成灰色并在当前栈没有扫描时将新对象也标记成灰色:同时,创建的所有新对象都标记成黑色。
Dijkstra插入写屏障需要gc在标记结束时STW。(插入写屏障会带来大量的额外开销,所以在实现上没有对栈上的对象开启,在扫描结束后会对栈上对象重新扫描)
Yuasa删除写屏障需要gc在标记开始时STW。

3.gc对应用的影响
- 开始结束阶段的STW停顿(控制在毫秒级)
- 25%的CPU用于后台标记
- 标记辅助(确保分配内存小于标记任务)
- 写屏障
- 后台懒清除(sweep)