4. 内存瓶颈定位:从泄漏到 OOM 的实战排查

内存问题,说实话,是实时系统里最难缠的对手。CPU 跑高了你能看到进程忙,IO 慢了你能看到磁盘在等,但内存泄漏——它就像温水煮青蛙,等你发现时,系统往往已经 OOM 了。

我个人习惯把内存排查分成三个层次:用量看趋势、分配看行为、泄漏看细节。这一章,我们就用 free、vmstat、smem 和 valgrind 这四把刀,一层层把内存问题剖开。

核心思路: 先宏观(free/vmstat)判断是否异常 → 再微观(smem)定位谁在吃内存 → 最后用 valgrind 深挖泄漏点。别一上来就上 valgrind,那是杀鸡用牛刀。
内存瓶颈排查三层架构 宏观监控 free / vmstat 进程级分析 smem / /proc/pid/smaps 深度检测 valgrind / AddressSanitizer 排查原则: • 先看总量(free -h),确认是否真的紧张 • 再看趋势(vmstat 1 10),观察内存变化速率 • 最后定位(smem + valgrind),精准打击泄漏点

4.1 free:三秒判断内存水位

free 是我每次登服务器敲的第一个命令。别小看它,三行输出就能告诉你系统是不是在“硬撑”。

# 常用方式
free -h              # 人类可读单位
free -s 2 -t         # 每2秒刷新一次,显示总计

输出里我最关注的是 available 这一列。它才是真正可用的内存,不是 free 那列。为什么?因为 Linux 会把空闲内存拿去做 cache/buffer,但需要时能立刻回收。

我的经验: available 低于总内存的 10% 就要拉警报了。如果同时看到 swap 使用量在涨,那基本可以断定——内存不够用了。

4.2 vmstat:看内存变化的“心电图”

vmstat 能告诉你内存的变化趋势。我习惯让它持续跑,观察几轮数据。

vmstat 1 5
# 输出示例:
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
#  r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
#  2  0 102400 345678  12345 678901   12    5    34    56  100  200 30 10 60  0  0

重点看两对指标:

  • si / so(swap in / swap out):如果这两个值持续非零,说明系统正在频繁换页。这是性能杀手。
  • free 列:持续下降且速度很快,说明有进程在“吃”内存。

我记得有一次线上告警,free 从 4G 掉到 200M 只用了 3 分钟。一看 si/so,每秒都在换页。那台机器的 Java 进程明显有泄漏。

注意: 别只看 free 列。Linux 的 free 值低不代表内存不够,因为 cache 占用了大量内存。一定要结合 available 和 swap 使用量来判断。

4.3 smem:谁在“偷”你的内存?

smem 比 top 和 ps 更准确。它考虑了共享内存,能给出每个进程的 USS、PSS、RSS。

# 安装
apt install smem   # 或 yum install smem

# 按物理内存使用排序
smem -r -k -s rss | head -20

# 只看某个用户
smem -u root -k
指标 含义 使用场景
USS 独占物理内存(不共享) 判断进程真实内存占用
PSS 按比例分摊共享内存 系统级内存分配分析
RSS 全部驻留物理内存(含共享) 快速看谁吃得多

我个人习惯用 PSS 做决策。RSS 会高估共享库的消耗,USS 又低估了。PSS 最接近真实情况。

4.4 valgrind:内存泄漏的“显微镜”

当怀疑某个进程有泄漏时,valgrind 是终极武器。它通过模拟 CPU 来检测每次内存分配和释放。

# 基本用法
valgrind --tool=memcheck --leak-check=full ./your_program

# 输出泄漏摘要
# ==12345== LEAK SUMMARY:
# ==12345==    definitely lost: 1,024 bytes in 10 blocks
# ==12345==    indirectly lost: 512 bytes in 5 blocks
# ==12345==    possibly lost: 0 bytes in 0 blocks
# ==12345==    still reachable: 0 bytes in 0 blocks

重点关注 definitely lost 和 indirectly lost。前者是直接泄漏,后者是因为指针丢失导致的级联泄漏。

避坑指南: 我曾经在生产环境直接对 Java 进程用 valgrind,结果进程启动慢了 20 倍,直接超时。后来学乖了——valgrind 只用在测试环境或压测阶段。线上用 /proc/pid/smaps 或 pmap 做初步判断就够了。

4.5 OOM 与 Swap:最后的防线

当内存耗尽,Linux 会启动 OOM Killer。它会根据 oom_score 杀掉一个进程。你想想看,如果被杀的是你的关键服务,那后果...

# 查看 OOM 日志
dmesg | grep -i oom
journalctl -k | grep -i oom

# 查看进程的 oom_score
cat /proc/<pid>/oom_score

Swap 是另一个“假救星”。很多人觉得开了 swap 就安全了,其实不然。当系统开始大量使用 swap,性能会断崖式下跌。因为磁盘 I/O 比内存慢几个数量级。

我的建议: 实时系统尽量关闭 swap,或者设置 vm.swappiness=1。让 OOM 早点发生,比让系统在 swap 里“苟延残喘”要好。早死早超生,监控能及时告警。

4.6 实战排查流程

最后,我总结一个排查流程,你可以直接拿去用:

  1. 第一步: free -h 看 available 是否低于 10%。
  2. 第二步: vmstat 1 5 看 si/so 是否非零,free 是否持续下降。
  3. 第三步: smem -r -k -s rss 找出 TOP 10 内存消耗进程。
  4. 第四步: 对可疑进程用 pmap -x <pid> 或 cat /proc/<pid>/smaps 看内存分布。
  5. 第五步: 测试环境用 valgrind 深挖泄漏点。
  6. 第六步: 检查 OOM 日志,确认是否有进程被误杀。

嗯,这套流程我用了很多年,基本没翻过车。内存问题不可怕,可怕的是没有排查思路。现在你有了。