协助标记 (Mark Assist) 是 Go GC 的一种背压 (Backpressure) 机制。当业务协程分配内存太快,导致 GC 扫描跟不上时,运行时会强制该协程“停工”,转去帮 GC 扫垃圾。

为什么需要协助标记?

在并发 GC 期间,系统面临一个经典的生产与消费平衡问题:

角色行为潜在风险
业务协程 (User)持续生产(分配内存)如果分配太快,堆内存会瞬间撑爆。
GC 后台协程 (Worker)持续消费(扫描标记)如果扫描太慢,回收速度赶不上增长速度。

如果任由业务协程“狂奔”,GC 还没标记完,程序就可能因为 OOM (Out of Memory) 崩溃。

核心逻辑:谁污染,谁治理

为了解决上述问题,Go 引入了 Mark Assist。它通过一套“信用分”系统来强制平衡速度:

行为对信用分 (Credit) 的影响结果
申请内存 (Malloc)扣分 (消耗信用)信用分为负时,协程被扣留。
协助标记 (Scan)加分 (赚取信用)必须通过“扫大街”赚分才能继续分配。

协助标记的作用

  1. 提供背压 (Backpressure):通过强制暂停分配过快的协程,从源头上限制内存增长速率。
  2. 加速标记:将原本只有几个后台协程在干的活,分摊给所有“高产”的业务协程,确保标记阶段按时完成。
  3. 防止 OOM:确保在堆内存触及目标阈值前,所有的存活对象都已被标记完成。

对应用的影响

  • 局部性影响:它不是 STW,只会影响那个“手速太快”的 Goroutine,其他协程正常运行。
  • 性能特征:是导致 P99 延迟 升高的主要原因。如果你发现某个接口偶尔响应极慢,可能就是它在帮 GC “打工”。

运行流程

graph TD
    A[Goroutine 申请分配内存] --> B{信用分是否足够?}
    B -- 足够 --> C[直接分配并扣分]
    B -- 不足 --> D[进入 Mark Assist 模式]
    D --> E[帮 GC 扫描一部分内存]
    E --> F[赚取信用分]
    F --> B

一句话总结:Mark Assist 就像是交警拦下了超速车辆,并要求司机下车帮扫大街,直到补齐“环保贡献”才能继续开车。