expvar 不显示自定义 gc 统计是因为未主动更新变量值,且 memstats.pausens 是含 256 次历史暂停的切片而非单值;需轮询 debug.gcstats.lastgc、定期计算均值并持久化,不可依赖 expvar 直出数据。

Expvar 注册自定义 GC 统计变量后不显示?
Expvar 默认只暴露 runtime.ReadMemStats 的快照,不自动刷新 GC 相关指标(比如上一次 GC 耗时、堆增长量)。你手动注册的变量如果没在每次 GC 后更新,expvar.Get("my_gc_pause_ns") 就一直返回初始值或零值。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
runtime.GC()触发后手动调用更新逻辑,但生产环境不能频繁调用——改用runtime.ReadGCStats+debug.SetGCPercent配合钩子更稳妥 - 推荐监听
debug.GCStats中的LastGC字段变化,用 goroutine 定期轮询(例如 100ms 间隔),避免阻塞主流程 - 别把
expvar.NewInt("gc_pause_ns")直接塞进runtime.GC回调——Go 没有 GC 回调机制,必须自己轮询对比时间戳
为什么 expvar.Get("memstats").String() 里看不到 GCPause?
memstats 是 runtime.MemStats 的只读快照,字段名是 PauseNs(切片),不是单个数值。Expvar 把它序列化成 JSON 数组,但前端监控工具常误以为这是标量。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 不要依赖
memstats.PauseNs做趋势图——它只存最近 256 次 GC 的纳秒级暂停时间,且每次 GC 后旧值会被覆盖 - 想看平均暂停时间?得自己算:取
PauseNs最后 N 个非零值,用time.Duration转换后再求均值 - 若需长期统计,务必另起 goroutine 持久化到本地文件或推送到 Prometheus,别只靠 expvar HTTP 接口查
用 expvar + curl 查 GC 数据时返回空对象?
常见错误是访问路径写成 /debug/vars/memstats 或 /debug/vars?name=memstats——Expvar 不支持路径参数或子路径查询,只认根路径 /debug/vars,所有变量都在一个 JSON 里返回。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
curl -s http://localhost:6060/debug/vars | jq '.memstats.PauseNs | last'提取最新 GC 暂停时间(需装 jq) - 浏览器直接打开
/debug/vars会触发下载,因为响应头是Content-Type: application/json; charset=utf-8,不是 text/html - 如果返回空 {},检查是否漏了
import _ "expvar"或http.ListenAndServe(":6060", nil)没启动
Prometheus 抓不到 expvar 的 GC 指标?
Expvar 输出是 JSON,而 Prometheus 只认文本格式的 name{labels} value timestamp。直接配置 scrape_configs 抓 /debug/vars 会解析失败。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 加一层转换:用
expvarmon工具(go install github.com/divan/expvarmon@latest),它能把 expvar JSON 转成 Prometheus 格式 - 或者写个轻量 handler:用
expvar.Do遍历所有变量,对匹配^gc_.*$的变量按 Prometheus 行协议输出 - 注意
expvar.NewFloat("gc_heap_kb")和expvar.NewInt("gc_count")类型不同,转文本时要分别处理,否则strconv.FormatFloat会 panic
GC 统计真正的难点不在采集,而在区分“暂停时间抖动”和“真实性能退化”——PauseNs 本身不带时间戳,你得自己关联 LastGC 才能判断某次暂停是否异常。这个对齐动作,90% 的人会漏掉。










