pprof Web UI默认不显示泄漏源头,是因为它默认展示alloc_space(总分配量),而泄漏只反映在inuse_space(当前存活内存);runtime.mallocgc占比高仅因其是分配入口,真实泄漏点需用-inuse_space+Focus+Call graph定位上层业务代码。

直接看 go tool pprof -http=:8080 生成的 Web UI,但必须用 -inuse_space 指标 + Focus 过滤 + View → Call graph 才能定位到真实泄漏点,光靠火焰图或默认 top 列表基本无效。
为什么 pprof Web UI 默认不显示泄漏源头
pprof 的 Web 界面默认展示的是 alloc_space(总分配量),而内存泄漏只体现在 inuse_space(当前存活对象占用内存)。你看到 runtime.mallocgc 占比最高,只是因为它是所有分配的入口;真正该盯的是它调用栈上层——比如某个 handler 闭包里 make([]byte, 1 后没释放,或 <code>sync.Map.LoadOrStore 不断塞入却从不 delete。
常见错误现象:
- 打开
http://localhost:8080后只看「Top」页,发现bytes.makeSlice排第一,就去改切片逻辑——其实它早被 GC 回收了 - 没点「View → Sample」手动切换指标,一直卡在默认的
flat或alloc_space视图 - 用
Focus *UserConfig后不点「Call graph」,只看扁平调用栈,结果只看到http.HandlerFunc.ServeHTTP,根本找不到谁 new 的它
如何用 Focus + Call graph 锁定业务代码行
当你在 Web UI 中确认某类对象(如 *model.Order)占用了大量 inuse_space,下一步不是翻源码猜,而是让 pprof 帮你画出引用链:
立即学习“go语言免费学习笔记(深入)”;
- 点右上角「Focus」输入
*model.Order(注意星号和类型名要完全匹配) - 点「View → Call graph」,会生成一张带权重的调用图,边粗细代表该路径下该类型对象的内存占比
- 重点看图中指向你 own package 的节点,比如
yourapp.(*OrderService).Process→json.Unmarshal→new(*model.Order) - 如果图中出现
runtime.selectgo或runtime.gopark,说明泄漏藏在 goroutine 里——顺着它的父节点往上找,大概率是某个go func() { ... }()闭包捕获了order变量
生成 SVG 调用图时必须加的参数
命令行直接导出 SVG 更适合存档或发给同事复核,但漏掉关键参数会导致图里全是 runtime 函数、业务代码全被折叠:
- 必须加
-inuse_space:否则默认按分配量画图,和泄漏无关 - 必须加
-focus "*model.Order"(引号不能少):不然图里混着几十种类型,无法聚焦 - 建议加
-nodecount=50和-nodefraction=0.01:避免只画顶层几个函数,漏掉中间关键跳转 - 命令示例:
go tool pprof -inuse_space -focus "*model.Order" -nodecount=50 -nodefraction=0.01 -svg heap.pb.gz > order_callgraph.svg
Call graph 里看到 runtime 函数就等于没找到根源
如果你导出的图里,从 new(*model.Order) 往上全是 runtime.malg、runtime.growslice、runtime.mapassign,说明 pprof 没抓到业务层调用点——大概率是因为:
- 对象不是你代码里
&model.Order{}显式创建的,而是被第三方库(如github.com/golang/groupcache)间接缓存并长期持有 - 采样时机不对:在 GC 刚触发后立刻抓快照,存活对象太少;或在刚启动时抓,堆里全是初始化数据
- 没做两次快照 diff:单次快照看不出增长趋势,
go tool pprof -base before.pb.gz after.pb.gz才能高亮新增驻留对象
真正有效的调用图,应该至少有一条路径能落到你的 .go 文件具体函数名上,而不是停在 runtime 层。如果始终落不到,优先查 map / sync.Map / channel / context.WithCancel 这四类持有结构。


















