直接回答:Go 的 GC 不是定时触发,而是由一个反馈控制器(pacer)按目标堆增长率(GOGC,默认 100,即堆翻倍)动态决定:每轮 GC 结束后设定下一轮的目标堆大小,分配到达目标即触发。pacer 还会估算扫描工作量,提前启动标记让其在目标点恰好收尾。mutator assist 是配套的“配额制度”:分配过快的 goroutine 在 GC 活跃期必须分担标记工作(assist),把 GC 成本摊到制造垃圾最多的人头上,防止分配速度跑赢回收速度。

展开解析:细节:GOGC=100 的含义是“新堆涨到 GC 结束时活堆的两倍再触发”,所以 GOGC 本质是内存与 CPU 的兑换率,调大到几百换吞吐,调小(或用 debug.SetMemoryLimit 软内存上限)省内存。pacer 的启动点不是死等目标——它根据历史标记速率提前量启动并发标记,否则标记未完成堆就超了。assist 的强度正比于分配速率超出配额的程度,表现为分配路径里的 gcAssistAlloc 检查,极端时 goroutine 一半时间在做标记,这是“分配越快自身越慢”的自稳定设计。可观测:GODEBUG=gctrace=1 输出每轮的墙钟、CPU 占用与目标达成情况;runtime/metrics 里 /gc/heap/goal 看当前目标。实践中 CPU 型服务 GC CPU 占比超 25% 就该考虑减少分配(对象复用、逃逸治理)或调 GOGC,而不是先怀疑 GC 本身。

追问方向:SetMemoryLimit 与 GOGC 冲突时谁优先?ballast(压舱物)技巧的利弊?

(约 490 字)