pprof heap/allocs 实战解读¶
要点卡¶
30 秒回答
heap 看当前仍在使用的分配(inuse),allocs 看累计分配(含已释放);排查泄漏偏向 heap,排查 GC 压力偏向 allocs。内存 profile 按分配字节概率采样,默认平均约每 512 KiB 采一个样本,不是机械地“每 512 KiB 必采一次”。
3 分钟展开
- 是什么:
/debug/pprof/heap暴露内存 profile;go tool pprof交互分析调用栈。 - 为什么:定位谁占内存、谁分配多;比
ReadMemStats多了栈归因。 - 怎么做:
?gc=1强制 GC 后快照;-sample_index=inuse_space;对比两次 heap 差分;allocs 看alloc_objects。
| 记忆槽 | 内容 |
|---|---|
| 三个不变量 | heap 看仍存活/在用,allocs 看累计分配;profile 是概率采样估算;差分必须绑定可比 workload |
| 手画图 | workload → allocation samples → heap/allocs → top/cum/list → code fix → same-workload verify |
| 项目落点 | 用实际索引器或 API 的内存峰值说明两次快照、流量窗口和版本如何对齐;只引用本人实际参与部分和可解释指标 |
| 一个取舍 | 降低采样间隔提高小热点可见性但增加开销;生产通常先用默认采样再做定向复现 |
错误表达
- ❌ “pprof 每 512 KiB 必采一个对象;
?gc=1后还在的都一定是泄漏。” - ✅ “采样间隔是平均概率;GC 后存活对象可能是合法缓存,必须结合增长趋势和引用所有权判断。”
自测追问:inuse_space 与 alloc_space 分别回答什么问题?为什么 base diff 不能替代相同 workload?
10 分钟版(原理 + 图示)¶
profile 类型
| 模式 | 含义 | 典型问题 |
|---|---|---|
| inuse_space | 当前占用字节 | 泄漏、缓存过大 |
| inuse_objects | 当前对象数 | 小对象过多 |
| alloc_space | 累计分配字节 | GC CPU 高 |
| alloc_objects | 累计分配次数 | 热点构造 |
采样机制:以 MemProfileRate(默认 512 KiB)为平均采样间隔做概率采样,再按权重估算总体。短时、小样本热点可能被低估;调低 rate 会提高开销,且应在尽可能早的启动阶段设置。
flowchart LR
Req[HTTP /debug/pprof/heap] --> GC[可选 gc=1]
GC --> Snap[堆快照]
Snap --> Pprof[go tool pprof]
Pprof --> Top[top / list / web]
常用命令
go tool pprof -http=:0 'http://localhost:6060/debug/pprof/heap?gc=1'
go tool pprof -sample_index=alloc_objects -top http://localhost:6060/debug/pprof/allocs
go tool pprof -base heap1.prof heap2.prof # 差分
读 top 输出:flat=自身,cum=子调用含自身;优先看 cum 找入口,flat 找具体分配点。
生产场景¶
- RSS 缓慢上涨:每周 heap inuse_space top 类型同一,怀疑 global cache。
- GC 占比 20%:allocs 显示
json.Unmarshal、fmt.Sprintf顶部。 - 可观测:定时抓 heap 存 S3,CI 对比回归。
排查与工具¶
| 工具 | 用途 |
|---|---|
go tool pprof |
top/list/web/flame |
pprof -base |
版本/时间差分 |
runtime/pprof |
代码内写文件 |
GODEBUG=gctrace |
与 allocs 交叉验证 |
路径:现象 → 选 heap 或 allocs → gc=1 快照 → top -cum → list 函数 → 代码修复 → 复抓对比。
架构取舍¶
| 方案 | 适用 | 不适用 |
|---|---|---|
| 常驻 pprof 端口 | 内网/debug 侧车 | 公网暴露 |
| 连续 profiling(Parca/Pyroscope) | 大规模 | 小服务 overhead |
| 压测时抓 profile | 可复现 | 仅生产偶发 |
| 降 MemProfileRate | 精确定位 | 生产长期开 |
深挖问答¶
- heap 与 allocs endpoint 区别? → 同一 profile 源,默认视图 index 不同。
- 为何
?gc=1? → 去掉待回收垃圾,inuse 更接近真实存活。 - flat 0 cum 高? → 分配在深层 callee,父函数 cum 高。
- 采样失真? → 大分配被采中的概率更高,但不能说“必记”;短时小对象热点需结合 bench、逃逸报告与更长采样。
- 生产 overhead? → 默认很低;rate 过小或频繁 gc=1 会增加成本。
反模式与事故¶
- 只看 alloc 不看 inuse,泄漏未被发现。
- 未
-base对比,把业务增长误判泄漏。 - pprof 端口暴露公网导致信息泄露与 DoS。
代码示例¶
import (
"net/http"
_ "net/http/pprof"
"os"
"runtime/pprof"
)
func main() {
go http.ListenAndServe("127.0.0.1:6060", nil)
f, _ := os.Create("heap.prof")
defer f.Close()
runtime.GC()
_ = pprof.WriteHeapProfile(f)
}