free 说内存爆了,MemAvailable 却说没事?读懂 /proc/meminfo
场景:free 显示"可用内存不足"、告警响了,应用却没崩——你根本不知道内存被谁吃了 路径:free 表象 → meminfo 逐行对账 → 内核回收原理 → 正确处方
症状识别
free 说内存"用完了",应用却稳如老狗
上篇讲了 irqbalance 配置不当导致单核 CPU 100%,这次我们把视角从 CPU 转到内存——内存类排查的第一篇。
某天凌晨,监控告警触发:服务器内存使用率超过 90%,free -h 显示 free 列只剩几百 MB。值班的同学慌了,准备重启应用——但应用日志一切正常,接口 RT 没涨,进程也没 OOM(Out Of Memory,内存耗尽时内核会杀掉进程)。
如果你遇到这种情况,第一反应大概率是"加内存"——但加完你会发现,内存照样被"用光"。因为问题根本不在物理内存不够,而在你盯错了指标。

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

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

核心是这三个字段:
MemTotal: 32097856 kB
MemFree: 353280 kB
MemAvailable: 19982336 kB
- MemTotal:物理内存总量,不含内核自己保留的部分,基本不变。
- MemFree:完全空闲的物理页数量。注意这只是"彻底没被用"的页,数字小不代表内存不够。
- MemAvailable:内核估算的"还能安全分配给新程序的内存"。它才是判断内存够不够的权威指标。
缓存两兄弟:Buffers / Cached
内存"消失"的大头在这里——上图里 Cached: 23456780 kB,占了 32GB 里的 22GB:
- Buffers:块设备(磁盘)的原始缓冲,很小。
- Cached:page 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 smem 或 apt 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 水位时触发直接回收。

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。
处方
正确评估内存的三个姿势
- 看 MemAvailable,不是 MemFree——
free -h的 available 列或直接 grep meminfo。 - 看 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,但生产环境慎用——释放后所有文件要重新读磁盘,反而拖慢业务。正确姿势:

上面的关键点是:先 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 把账算清楚,而不是凭直觉加机器。