写屏障(Write Barrier)是编译器在指针赋值操作中插入的一段额外逻辑用于在并发标记阶段追踪指针变化,确保 GC 不会遗漏任何存活对象。
痛点:为什么需要写屏障?
在 三色标记法 的并发阶段,业务代码和 GC 扫描同时运行。如果没有监控,会发生“对象丢失”:
| 场景步骤 | 动作描述 | 后果 |
|---|---|---|
| 1. 扫描完毕 | GC 已将对象 A 标记为黑色(已扫描) | A 不会再被扫描 |
| 2. 指针变动 | 业务代码让 A 指向了一个白色对象 B | GC 看不到 B |
| 3. 引用断开 | B 的其他原始引用被删除 | B 成了孤儿 |
| 结果 | B 被误判为垃圾回收掉 | 程序崩溃 (Dangling Pointer) |
对策:混合写屏障 (Hybrid Write Barrier)
Go 1.8+ 采用混合写屏障,结合了“插入”和“删除”两种策略的优点,实现了栈上不开启写屏障的极致性能。
核心逻辑
当执行 *slot = ptr (将指针 ptr 写入内存 slot) 时:
| 策略类型 | 动作 (Shade) | 目的 |
|---|---|---|
| 删除屏障 | 标记 *slot (旧值) 为灰色 | 保护被删除引用的对象,确保本轮存活 |
| 插入屏障 | 标记 ptr (新值) 为灰色 | 保护新引用的对象,防止被 GC 漏掉 |
伪代码实现
// 编译器自动插入的逻辑
write_barrier(slot, ptr):
shade(*slot) // 标记旧对象为灰色
shade(ptr) // 标记新对象为灰色
*slot = ptr // 真正的赋值操作生命周期
写屏障并非全程开启,它仅在 GC 的特定阶段工作:
- 开启:在
Mark Prep阶段的短暂 STW 期间开启。 - 运行:在整个 并发标记 (Concurrent Mark) 阶段持续监控。
- 关闭:在
Mark Termination阶段的 STW 期间关闭。
性能权衡
| 维度 | 影响 | 优化对策 |
|---|---|---|
| CPU 开销 | 每次指针赋值增加几条指令 | 仅在堆 (Heap) 上开启,栈 (Stack) 上不开启。 |
| 内存一致性 | 确保并发安全 | 配合 STW 进行最后的栈扫描。 |
一句话总结:写屏障通过在指针修改时“多做一点事”,换取了 GC 不需要长时间停顿程序的能力。