4、内存性能分析:内存使用率、内存泄漏、Swap使用、缺页异常、大页内存

内存,说白了就是系统的「高速工作台」。CPU 要干活,数据得先放到内存里。一旦内存不够用或者用得不合理,整个系统的性能就会断崖式下跌。我这些年排查过的线上故障,至少有三成跟内存有关。今天咱们就把内存性能分析这五个核心点掰开揉碎讲清楚。

4.1 内存使用率:别被数字骗了

很多人一上来就看 free -h,看到用了 90% 就慌了。其实这不一定有问题。Linux 的内存管理有个特点:它会把空闲内存拿来当文件缓存,目的是加速磁盘 I/O。

关键指标区分:

  • used:真正被进程占用的内存
  • buff/cache:内核用做缓存的内存,可以回收
  • available:新进程可以使用的内存(包含可回收的缓存)

我个人习惯,先看 available 而不是 used。举个例子:

# 查看内存概览
$ free -h
              total        used        free      shared  buff/cache   available
Mem:           31Gi        18Gi       2.1Gi       1.2Gi        11Gi        12Gi
Swap:         4.0Gi       0.0Gi       4.0Gi

你看,used 是 18G,但 available 还有 12G。这意味着系统其实还有余量。我在项目中遇到过有人看到 used 90% 就急着加内存,结果加了之后发现根本用不上——白花钱。

我的习惯:当 available 低于总内存的 10% 时,才需要真正警惕。这时候缓存已经快被榨干了。

4.2 内存泄漏:看不见的「黑洞」

内存泄漏是最让人头疼的问题之一。进程申请了内存,用完不释放,就像水龙头一直开着不关。日积月累,系统内存就被慢慢吃光。

怎么发现?我一般用 tophtop 看进程的 RES(常驻内存)是否持续增长。更专业的做法是用 valgrindAddressSanitizer

# 用 top 观察进程内存变化
$ top -p <pid>
# 重点关注 RES 列,如果每隔几秒就涨一点,大概率泄漏了

我曾经排查过一个 Java 应用的内存泄漏。进程跑了三天,RES 从 2G 涨到了 12G,最后 OOM 被 kill 了。用 jmap 导出堆转储文件,发现是一个 HashMap 缓存没有设置淘汰策略,越积越多。

避坑指南:我曾经以为只有 C/C++ 才会泄漏。其实 Java、Go 甚至 Python 都可能泄漏——只要你有全局缓存、线程池或者未关闭的文件句柄。

4.3 Swap 使用:性能的「减速带」

Swap 是磁盘上的一块空间,用来当内存的「备胎」。内存不够时,内核会把不常用的内存页换到磁盘上。问题是——磁盘比内存慢几个数量级。

我判断 Swap 是否有问题的标准很简单:

  • Swap 使用量高但稳定:可能只是系统把不常用的数据换出去了,问题不大
  • Swap 持续增长或频繁换入换出:这是大问题,说明物理内存严重不足
# 查看 Swap 使用情况
$ 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
 1  0 204800 1024000 50000 2000000  100  200   500   300  200 300 10  5 80  5  0

注意 si(swap in)和 so(swap out)这两列。如果它们持续不为 0,说明系统正在频繁换页。我在项目中遇到过一台数据库服务器,si/so 每秒几百 KB,结果查询延迟从 5ms 飙升到 500ms。关掉 Swap 后,性能立刻恢复。

我的建议:对于数据库、缓存这类延迟敏感的应用,最好把 swappiness 调低甚至关掉 Swap。

# 临时关闭 Swap
$ swapoff -a
# 永久关闭:注释掉 /etc/fstab 中的 swap 行

4.4 缺页异常:少不了的「小插曲」

缺页异常(Page Fault)是内存管理的正常机制。进程访问的页面不在物理内存中,内核就得去磁盘加载。这本身不是问题,但频率过高就会拖慢性能。

缺页分两种:

  • 轻微缺页(minor fault):页面在内存中,只是页表没映射。开销很小。
  • 严重缺页(major fault):页面在磁盘上,需要 I/O 读取。开销很大。
# 用 ps 查看进程的缺页情况
$ ps -o minflt,majflt,cmd -p <pid>
MINFLT  MAJFLT CMD
  12345      10 /usr/bin/mysql

重点关注 majflt。如果这个数字持续增长,说明进程频繁访问磁盘上的数据。嗯,这时候你得想想:是不是内存太小了?或者代码的局部性不好?

我记得有一次优化一个视频转码服务,发现 majflt 每秒几百次。后来把工作集大小调小,同时启用大页内存,majflt 降到了个位数,转码速度提升了 3 倍。

4.5 大页内存:给「大块头」开绿灯

标准内存页大小是 4KB。对于需要大量内存的应用(比如数据库、虚拟机),管理几百万个 4KB 页会让 TLB(页表缓存)不堪重负。大页内存(Huge Pages)把页大小提升到 2MB 甚至 1GB,减少了页表项数量,TLB 命中率大幅提升。

启用大页内存有两种方式:

  1. 传统大页(HugeTLB):需要预先分配,重启后失效
  2. 透明大页(THP):内核自动管理,但可能引入延迟
# 查看当前大页配置
$ cat /proc/meminfo | grep -i huge
AnonHugePages:   1048576 kB
HugePages_Total:    1024
HugePages_Free:      512
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB

我的经验:对于数据库(MySQL、PostgreSQL)和 Java 应用(特别是 G1GC 垃圾回收器),启用大页内存通常能带来 10%-30% 的性能提升。但要注意,透明大页在某些场景下会导致内存碎片和延迟抖动,我建议数据库场景用传统大页。

# 分配 1024 个 2MB 大页(共 2GB)
$ echo 1024 > /proc/sys/vm/nr_hugepages
# 让 MySQL 使用大页
$ mysqld --large-pages

知识体系总览

下面这张图把内存性能分析的五个核心维度串起来了。你可以把它当作排查问题的「导航图」:

内存性能分析五维模型 内存性能 分析核心 内存使用率 used vs available buff/cache 可回收 内存泄漏 RES 持续增长 valgrind / jmap Swap 使用 si/so 换入换出 缺页异常 minor vs major majflt 是关键 大页内存 HugeTLB / THP TLB 命中率提升 排查顺序:先看使用率 → 再查泄漏 → 检查 Swap → 分析缺页 → 优化大页

这张图里,五个维度是相互关联的。比如内存泄漏会导致可用内存减少,进而触发 Swap 和缺页异常。我建议你排查时按这个顺序来:先看使用率,再查泄漏,然后检查 Swap,接着分析缺页,最后考虑大页优化。

一句话总结:内存分析不是看一个指标就下结论。你得把使用率、泄漏、Swap、缺页、大页这五块拼起来,才能看到全貌。就像医生看病,不能只看体温,还得看血常规、CT 片子。


公众号:蓝海资料掘金营,微信deep3321