首页/系统/Linux 内核与性能调优速查/应用内存泄漏定位(smaps 差分)
内存与 OOM58 条

应用内存泄漏定位(smaps 差分)

对进程做 smaps_rollup 定期采样做差:anon/private 持续增长指向应用分配泄漏,file 增长多为缓存;再用 pmap 找具体增长段。

定位思路

应用内存泄漏定位的实用方法是差分:对目标进程的 smaps_rollup 按固定间隔连续采样(至少三次,并跨越一个完整业务周期),看哪类字段在单调增长——Anonymous 与 Private_Dirty 持续增长指向应用分配泄漏,file 类增长多为映射缓存。确认增长后,用 pmap -x 对比具体内存段缩小范围,最后交给应用侧工具(堆分析、GC 日志)才能定位到代码。

基本信息

采样接口/proc/<pid>/smaps_rollup(读取开销小)
采样建议间隔 1-5 分钟,连续 3 次以上并跨过完整业务周期
判读口径Anonymous/Private_Dirty 增长多为分配;file 类多为缓存
下钻工具pmap -x、/proc/<pid>/maps
容器视角cgroup memory.stat 的 anon/file/shmem 构成
应用侧互证堆指标、GC 日志、对方语言的堆外内存统计

典型使用场景

  • 容器内存缓慢上涨并触发限额告警,需要确认是进程泄漏还是缓存
  • 长稳测试的内存回归验证:发布前后用同一套采样流程对比
  • 托管语言应用判断增长发生在堆内还是堆外
  • 区分正常缓存增长与真实泄漏,避免误报工单
  • 为应用团队提供可复现的采样数据与时间线

采样与下钻

采样与下钻
for i in 1 2 3; do date; grep -E '^(Rss|Pss|Anonymous|Private_Dirty)' /proc/12345/smaps_rollup; sleep 60; done  # 连续采样
sudo pmap -x 12345 | sort -k3 -rn | head  # 按 RSS 排序找到最大的内存段
cat /proc/12345/maps | grep -E 'heap|anon' | head  # 查看可疑映射区间
grep -E '^(anon|file|shmem) ' /sys/fs/cgroup/myapp.slice/memory.stat  # 容器视角的构成
cat /proc/12345/status | grep -E 'VmRSS|VmSwap'  # 与 status 口径交叉核对
pidstat -r -p 12345 1 3  # 看缺页速率,判断增长是否伴随新分配

增长信号判读

观察对象增长含义下一步
Anonymous / Private_Dirty应用自身的分配高度怀疑泄漏,转应用侧堆分析
Rss总常驻内存需结合构成判断来源
file 类映射文件页缓存通常不算泄漏,可在压力下被回收
Swap已换出数据量增多说明物理内存吃紧而非泄漏本身
必须先排除缓存与 GC 波动:托管语言的堆会随回收周期起落,采样窗口要覆盖完整周期。只看相隔很短的两次读数,很容易把正常的周期性波动当成泄漏。
托管语言的泄漏可能发生在堆外(JNI、直接内存、映射文件)。内核侧看到持续增长而堆指标平稳时,方向应转向堆外分配,而不是继续怀疑垃圾回收配置。

易错点

  • 采样窗口太短或未跨业务周期:把 GC 与缓存波动误读为泄漏
  • 只看 Rss 不看构成:缓存增长被当成泄漏,浪费大量排查时间
  • 不与应用侧指标互证就下结论:内核侧只能证明「内存在涨」,不能证明「哪里没释放」
  • 在容器里只看 cgroup 数据:能确认总量增长,却定位不到具体进程
  • 用重启或清缓存掩盖现象:问题消失的同时证据也被抹掉,下次仍会复发

数据来源与参考

  • Linux 内核 Documentation/filesystems/proc.rst
  • procps-ng 文档 pmap(1)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌