free 说内存爆了,MemAvailable 却说没事?读懂 /proc/meminfo

场景:free 显示"可用内存不足"、告警响了,应用却没崩——你根本不知道内存被谁吃了 路径:free 表象 → meminfo 逐行对账 → 内核回收原理 → 正确处方


症状识别

free 说内存"用完了",应用却稳如老狗

上篇讲了 irqbalance 配置不当导致单核 CPU 100%,这次我们把视角从 CPU 转到内存——内存类排查的第一篇。

某天凌晨,监控告警触发:服务器内存使用率超过 90%,free -h 显示 free 列只剩几百 MB。值班的同学慌了,准备重启应用——但应用日志一切正常,接口 RT 没涨,进程也没 OOM(Out Of Memory,内存耗尽时内核会杀掉进程)。

如果你遇到这种情况,第一反应大概率是"加内存"——但加完你会发现,内存照样被"用光"。因为问题根本不在物理内存不够,而在你盯错了指标

free -h 显示内存不足

free 是查看内存使用的最常用命令。上面输出里 free 列只剩 345Mi,看起来确实要爆了。但注意 available 列还有 19Gi——同一时刻,两个数字差了 50 多倍,到底哪个才是真相?

              total        used        free      shared  buff/cache   available
Mem:           30Gi       7.8Gi       345Mi       224Mi        22Gi         19Gi
Swap:         8.0Gi          0B       8.0Gi
  • total / used / free:整机总量 / 已用 / 空闲。
  • buff/cache:Buffers + Cached,文件缓存,可回收
  • available:对应 meminfo 的 MemAvailable,内核估算"还能分给新程序的内存"——它才是判断内存够不够的真值,不是 free 列

监控平台按 (total - free) / total 算使用率,所以 free 列低就告警——它没把"可回收的 page cache"算进去。

三个典型表现

内存"告警但没崩"基本是这三种情况:

表现 特征 真凶
可用内存告警但无 OOM free 列极低,应用正常 page cache 占大头,可回收
缓存吃掉内存 buff/cache 列占 80%+ 文件缓存未回收
加机器没用 加了内存过几天又告警 新内存又被 page cache 吃掉(见定位段)

别急着加内存——先问"内存去哪了"

Naive 的做法是加机器、重启。有经验的工程师会先跑 freeavailable 列。而正确做法是:打开 /proc/meminfo,把每一行当账本一样对一遍,看看内存到底被谁吃了。

Linux 内存记账:每行都是一类占用


诊察

三巨头:MemTotal / MemFree / MemAvailable

cat /proc/meminfo 是最权威的内存账本,内核每秒钟都在更新它。一整份长这样(这台 32GB 机器):

cat /proc/meminfo 完整账本

核心是这三个字段:

MemTotal:       32097856 kB
MemFree:          353280 kB
MemAvailable:   19982336 kB
  • MemTotal:物理内存总量,不含内核自己保留的部分,基本不变。
  • MemFree:完全空闲的物理页数量。注意这只是"彻底没被用"的页,数字小不代表内存不够。
  • MemAvailable:内核估算的"还能安全分配给新程序的内存"。它才是判断内存够不够的权威指标

缓存两兄弟:Buffers / Cached

内存"消失"的大头在这里——上图里 Cached: 23456780 kB,占了 32GB 里的 22GB:

  • Buffers:块设备(磁盘)的原始缓冲,很小。
  • Cachedpage cache(页缓存),文件读写时内核把磁盘数据缓存到内存。这一项是内存占用大户——上面 23GB 内存都是它。

对账公式:内存到底去哪了

MemTotal 减去几大去向,就能把账对上:

MemTotal ≈ MemFree + Cached + Buffers + Slab + AnonPages + PageTables + KernelStack

实战中不用手算,free -h 已经按这个公式分组了:used = total - free - buff/cache。关键认知是——Cached 里绝大部分(约 90%)是内核可以在内存紧张时立即回收的,所以 MemAvailable 才会远大于 MemFree

内核自己吃的:Slab / SReclaimable / PageTables

除了进程内存,内核自己也占一份(见截图 Slab: 1456224 kB 等行):

  • Slab:内核对象缓存(inode、dentry 等)。
  • SReclaimable:Slab 中可回收的部分(如 dentry cache)。
  • SUnreclaim:不可回收的内核对象。
  • PageTables:页表,进程地址空间映射的"目录"。
  • KernelStack:每个线程的内核栈(8KB/线程),线程多时不可小看。

Active / Inactive、anon / file:谁在动

截图里这六行(Active 8934016 / Inactive 11545600 / Active(anon) 2345000 / Inactive(anon) 123456 / Active(file) 6589016 / Inactive(file) 11422144,单位 kB)揭示了内核的"冷热账":

  • Active / Inactive:按最近访问时间分热/冷。回收优先回收 Inactive。
  • anon(匿名内存):进程堆、栈、匿名 mmap,回收只能靠换出到 swap 或杀进程,成本高。
  • file(文件页):page cache,直接丢弃或写回即可回收,成本低。

这就是内核回收的"先 file 后 anon"策略——文件页说丢就丢,匿名页得先写 swap。


路径 — 下次遇到直接跑

三步定位命令组合

三步定位命令组合

smem 是比 ps 更准的内存统计工具,它考虑了共享内存的分配,不会把一份共享库算给每个进程(部分发行版需先安装:yum install smemapt install smem)。跑出来的输出长这样:

  PID User     Command                         Swap      USS      PSS      RSS
 4567 app      java -Xms8g -Xmx8g             0K      2.1G     2.3G     3.2G
 3321 root     mysqld                         0K      890M     980M     1.4G
 1208 redis    redis-server                    0K       88M     96M      112M
  987 app      nginx: worker                   0K       32M     34M       46M
  • RSS:这个进程占了多少物理内存(含共享部分)。
  • PSS:把共享内存按进程数均摊后的值,更能反映"这个进程真实吃掉多少"。
  • USS:独占内存,不跟任何人共享的部分。

排内存大户看 PSS 列最准——否则几个 Java 进程共享同一份类库,RSS 会被重复计算好几遍。

指标判读速查表

指标 正常 异常
MemAvailable 剩余越多越好,≥ total 的 20% 安全 持续 < 10% 且波动大,警惕 OOM
Cached 随文件读写波动 持续占用 90%+ 且不可回收才需关注
Slab SReclaimable 为主 SUnreclaim 持续增长 = 内核对象泄漏
SwapFree swap 不该被用 swap 使用 > 0 且持续 = 内存真的不足

定位 — 内核为什么"扣着"这么多内存不放?

page cache 回收机制

内核不是"吃掉"了你的内存,而是物尽其用:空闲的物理页闲着也是闲着,干脆拿来缓存文件数据,加快下次读取。这就是 Cached 常年居高不下的原因——也是"加内存没用"的根因:你加进去的内存,只要有一瞬间空闲,马上又被拿去缓存文件了,free 列照样低。

内核内存回收决策路径

当内存紧张时,内核的 kswapd 内核线程按这个顺序回收: 1. 先回收 Inactive(file) —— 丢弃干净的文件页 2. 再回收 Active(file) —— 把脏页写回磁盘后丢弃 3. 最后才碰 anon —— 写 swap 或等 OOM killer

watermark 与水位线

内核用 cat /proc/zoneinfo 看水位,回收不是到 0 才开始,而是到了 high 水位提前动手,低于 min 水位时触发直接回收。

zoneinfo 水位:free 落在 min 与 low 之间,kswapd 后台回收中

Node 0, zone   Normal
  pages free    100000
        min       88893
        low      111116
        high     133339
  • min:低于它就触发直接回收(进程卡顿)。
  • low:kswapd 开始异步回收。
  • high:回收的目标水位。

内核版本说明:/proc/meminfo/proc/zoneinfo/proc/sys/vm/drop_caches 这些路径自 2.6 内核起稳定存在,cgroup v1/v2 环境下均可用。不同内核的 zone 字段可能略有增减,字段名以本机 cat /proc/zoneinfo 实际输出为准。

上面这台机器 Normal zone 的 free 页数(100000)正落在 min 与 low 之间——说明 kswapd 正在后台回收,但因为可回收的 file cache 还有很多,所以 MemAvailable 依然有 19Gi,应用毫无感知。

为什么 MemFree 低不一定危险,什么时候才真该慌

MemFree 低只是说明"空闲页少",不代表"内存不够"。真正危险的是 MemAvailable 逼近 0 + swap 持续进出 + 大量 anon 不可回收——这时候才可能触发 OOM killer。

判断标准:MemAvailable / MemTotal < 10%SwapFree 持续下降,才是真告警。

这个案例属于系统层还是应用层? "告警但没崩、free 低而 available 高"——这是系统层的内存管理行为(page cache 占大头且可回收),不是应用层代码问题。反过来,如果 anon 持续增长、PSS 大户是某个 Java 进程、MemAvailable 跟着往下走,那才是应用层吃内存,要回去查进程堆和连接池。分层决定下一步跑什么命令:系统层查 meminfo/zoneinfo,应用层查 smem/smaps。


处方

正确评估内存的三个姿势

  1. 看 MemAvailable,不是 MemFree——free -h 的 available 列或直接 grep meminfo。
  2. 看 swap 进出——vmstat 1 持续看几秒,vmstat 是查看系统 CPU/内存/IO 实时统计的命令,si/so 列是每秒从磁盘换入 / 换出 swap 的字节量,持续 > 0 说明内存真的紧:
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      0 353280  88624 23456780    0  512   480   120 1200 2400  6  4 90  0  0

上面 so=512 表示这一秒从内存换出了 512KB 到磁盘——虽然不大,但持续出现就是警告信号,说明 anon 内存正在被压缩回收。 3. 看 SUnreclaim 趋势——watch cat /proc/meminfo,SUnreclaim 只增不减 = 内核对象泄漏。

drop_caches 正确用法(生产注意)

echo 3 > /proc/sys/vm/drop_caches 可以立即释放 page cache,但生产环境慎用——释放后所有文件要重新读磁盘,反而拖慢业务。正确姿势:

drop_caches 正确用法

上面的关键点是:先 sync 把脏页落盘,再 echo 1 只释放页缓存(值 1=page cache,2=dentries/inodes,3=全释放)。

内存排查路线图

场景 命令组合
内存告警但没崩 free -h → meminfo → 看 available
怀疑进程内存大 smem → /proc/PID/smaps 逐段看
怀疑内核泄漏 watch meminfo → 盯 SUnreclaim
怀疑真的 OOM dmesg 搜 "Out of memory" → 看被杀进程

dmesg 是内核日志查看命令,OOM killer 杀掉进程时会在这里留下完整的英文记录(进程名、内存用量、杀它的原因),是确认 OOM 的第一现场。


附:完整命令清单

free -h                       # 第一眼:看 available
cat /proc/meminfo             # 账本:逐行对账
smem -r -k -s rss             # 进程视角:按 RSS 排序
vmstat 1                      # 动态:看 si/so swap 进出
cat /proc/zoneinfo            # 水位:看 min/low/high
sync && echo 1 > /proc/sys/vm/drop_caches   # 谨慎:释放 page cache

阅读路线

内存类排查共 9 篇,本篇是开篇。下一篇我们聊 Linux OOM Killer 杀了你的 Java 进程?日志里有线索——当 MemAvailable 真的见底、进程被内核干掉时,dmesg 里的那几行英文到底说了什么。

记住一个心法:Linux 不会无缘无故"用光"内存——它只是在等你找到哪个占用真的不可回收。 排查内存问题的第一步,永远是打开 /proc/meminfo 把账算清楚,而不是凭直觉加机器。