godebug=gctrace=1 会实时输出每次gc的堆大小变化、暂停时间、标记/清扫耗时等关键指标到stderr,需启动前设置且注意输出捕获;gctrace=2 增加各代对象统计,适合生产环境;gcpacertrace=1 输出调度预测信息,仅用于深度调优。

GODEBUG=gctrace=1 能看到什么
它会把每次 GC 的关键指标实时打到标准错误输出,包括堆大小变化、暂停时间、标记/清扫耗时。不是日志,是 stderr 流式输出,所以得重定向或配合 stderr 捕获工具看。
常见错误现象:开了 GODEBUG=gctrace=1 却没看到输出——大概率是程序没触发 GC,或者输出被日志库吞掉了(比如用 log 包默认不打印 stderr);也可能是容器里 stderr 没挂载出来。
- 必须在启动前设置:
GODEBUG=gctrace=1 ./myapp,运行中设无效 - 输出不含时间戳,需自己加
stdlog.SetFlags(log.LstdFlags)或用script/ts工具补 - 每行以
gc #开头,后面数字是 GC 次数,别把它当时间序列编号
GODEBUG=gcpacertrace=1 为什么很少用
它输出的是 GC 暂停前的“调度预测”过程,比如 pacer 怎么估算下一次 GC 应该在什么时候触发、目标堆大小设多少。对调优有帮助,但信息太底层,绝大多数线上问题用不上。
使用场景仅限于:你怀疑 GC 触发太早(频繁小 GC)或太晚(堆暴涨后一次大停顿),且已排除内存泄漏和对象分配速率异常。
立即学习“go语言免费学习笔记(深入)”;
- 输出量极大,单次 GC 可能打出几十行,不适合长期开启
- 依赖 runtime 内部状态,Go 1.21+ 中部分字段语义有调整,文档没同步,容易误读
- 和
gctrace同时开时,两组输出混在一起,需靠前缀区分:gc #vspacer:
生产环境只开 gctrace=2 的真实原因
GODEBUG=gctrace=2 会在每次 GC 后额外打印当前堆中各代对象统计(如 tiny alloc 数、span 数、heap_alloc/heap_sys 差值),比 =1 多一层内存结构视角,又不像 =5 那样爆炸式输出。
性能影响几乎可忽略——GC 本身开销远大于多打几行字符串。但兼容性要注意:Go 1.19 之前 =2 和 =1 行为一致,1.19+ 才真正启用增强统计。
- 别用 =5:每轮 GC 输出超百行,IO 拖慢 STW,某些低配容器会直接卡住
- 如果用 systemd 启动,记得在
StandardError=journal外加LimitNOFILE=65536,避免 stderr buffer 溢出丢数据 - gctrace=2 的最后一行有
scvg:字段,那是系统内存回收(scavenger)动作,和 GC 无关,别误判为 GC 停顿
怎么把 gctrace 输出转成可观测数据
不能直接喂给 Prometheus——它不是结构化日志。得先用轻量工具做解析,再暴露指标。最稳的方式是用 grep + awk 提取关键字段,写进临时文件供 exporter 读。
示例命令流:GODEBUG=gctrace=2 ./myapp 2>&1 | awk '/gc #[0-9]+:/ {print "gc_count", $2; print "pause_ns", $4}' > /tmp/gc.metrics,再让 textfile_collector 定期扫这个文件。
- 别用正则硬匹配
pause=:Go 1.22+ 输出格式微调,pause=后面可能带单位(如pause=1.2ms),旧脚本会崩 - gctrace 不输出 goroutine 数或栈大小,想监控这些得另接
/debug/pprof/goroutine?debug=1 - 如果应用用了
os/exec启子进程,子进程不会继承父进程的GODEBUG,得显式传过去
GC 指标真正难的不是采集,是区分“正常抖动”和“异常征兆”——比如 pause 时间偶尔到 5ms 没事,但连续 10 次都 >2ms,且 heap_alloc 涨速变缓,那大概率是标记阶段被阻塞了,得查是不是有大量 finalizer 或阻塞的 Goroutine 在等锁。











