pprof显示runtime.mallocgc占比最高是正常现象,因其为所有堆分配统一入口;真正根源在调用栈上层业务代码,需用top -cum分析累积占比,再通过list定位具体行号,如buf := make([]byte, 1024)等高频分配语句。

pprof 本身不分析逃逸,只反映实际堆分配行为;所谓“内存逃逸”是编译期静态分析结论,而 pprof 抓的是运行时真实存活对象——你看到的 inuse_space 增长,就是泄漏,不是逃逸。
pprof 显示 runtime.mallocgc 占比最高,怎么找业务代码?
这是正常现象:runtime.mallocgc 是所有堆分配的统一入口,它本身不是问题,只是个“中转站”。真正要盯的是它的调用栈上层。
- 别在交互式命令行里直接
top,先执行top -cum,看累积占比最高的业务函数 - 用
list your_package.(*YourStruct).Method定位到具体行号,比如某行buf := make([]byte, 1 在循环里反复 new - 如果调用栈里全是
runtime.selectgo、runtime.gopark,说明泄漏藏在 goroutine 持有引用里,重点查 channel 接收后是否存入全局 map/slice、或闭包捕获了大结构体 - 注意:HTTP handler 里写
data := loadBigData(); http.HandleFunc(..., func() { handle(data) }),data就会被闭包长期持有
两次 heap 快照 diff 总是看不出增长,哪里错了?
常见原因是采样时机或参数选错,导致对比失效。
- 绝对不要用
?debug=1(文本格式)做 diff:它只含摘要,丢失调用栈深度,go tool pprof -base无法识别差异 - 必须用二进制格式且带正确后缀:
wget "http://localhost:6060/debug/pprof/heap?inuse_space" -O before.heap,文件名含.heap或.pb.gz - 别加
?gc=1:它强制 GC 后采样,会把本该存活的对象“刷掉”,掩盖泄漏 - 采样间隔至少 2 分钟,且程序需处于稳定负载——刚启动、GC 高频、或
MemStats.NextGC接近HeapAlloc时,快照全是临时对象
为什么 top 看到 *UserConfig 占 150MB,list 却找不到分配点?
因为 top 统计的是当前驻留内存总和,list 显示的是函数内所有分配(含已 GC 的),两者口径不同。你代码里很可能根本没写 &UserConfig{}。
- 典型间接创建场景:
json.Unmarshal解析进全局map[string]*UserConfig但 key 永远不删 -
sync.Map.LoadOrStore("id", cfg)后忘记清理过期项 - slice 截取后赋给全局变量:
globalBuf = b[:n],底层数组仍被引用 - 此时必须用
go tool pprof -http=:8080 binary before.heap after.heap,打开后点 View → Difference,再 Focus *UserConfig,才能看到谁在 new 它且没释放
最易被忽略的一点:pprof 默认不开启 heap 采样,首次访问 /debug/pprof/heap 才开始累积数据。上线后不立刻抓一次基准快照,后续所有对比都可能失真。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











