2024 年,我盯着一台线上服务器的监控,越看越慌。这是一台 16G 内存的机器,我习惯性地 SSH 上去 free -m 看了一眼,结果差点没坐稳:free(空闲)那一列,只剩下两百多兆。16G 的内存,只剩两百多兆是"空"的,在我的理解里,这台机器已经一只脚踩在悬崖边上了——再来一点点内存请求,它就要 OOM、就要把进程杀掉了。我立刻去抓内存泄漏:把机器上所有进程的内存占用(RSS)加起来,可怎么算,所有进程加一块也才六七个 G。机器明明只剩两百多兆空闲,可进程们加起来只用了六七个 G——那中间还有将近 10 个 G 的内存,到底被谁吃掉了?我查不到任何一个进程对它负责。我一度怀疑是内核漏内存,或者有什么"隐形进程"在偷偷吃。我反复 free、反复 ps,那将近 10 个 G 就是凭空消失了,既不在 free 里,也不在任何进程名下。一台内存"快满了"、却找不到是谁占的机器——这个矛盾把我困了很久。最后我才意识到:我从一开始就把 free 这个命令输出里的 free 那一列,理解错了;那将近 10 个 G,根本没有"消失",它就在我眼前,只是我不认识它的名字。这件事逼着我把 free 命令、buff/cache、available、Linux 的内存管理这一整套彻底理清了。本文复盘这次实战。
问题背景
环境:CentOS 7,16G 内存的应用服务器
事故现象:
- ★ free -m 显示 free(空闲)只剩两百多兆
- 我以为机器内存快耗尽、马上要 OOM
- ★ 可所有进程 RSS 加起来才六七个 G,差 10G 找不到
现场排查:
# 1. free 一看,空闲列吓人
$ free -m
total used free shared buff/cache available
Mem: 15885 6402 243 180 9240 9020
# ^^^ ★ free 只剩 243M
# ^^^^ ★ buff/cache 占了 9240M
# ^^^^ ★ available 还有 9020M
# 2. ★ 把所有进程的内存加起来 —— 对不上
$ ps aux --sort=-rss | head -8
USER PID %MEM RSS COMMAND
app 8901 18.0 2900000 java
app 9020 12.0 1950000 java
mysql 7711 9.0 1480000 mysqld
...
# ★ 全加起来才 ~6.5G —— 那 9G 的 buff/cache 不属于任何进程
# 3. ★ buff/cache 到底是什么 —— 看 /proc/meminfo
$ cat /proc/meminfo | grep -E 'Cached|Buffers'
Buffers: 210000 kB
Cached: 9030000 kB # ★ 9G 都是 Cached(页缓存)
# 4. ★ 关键验证:这块内存"能不能要回来"
# 看 available 那一列 —— 9020M。这才是"真正还能用的"。
根因(后来想清楚的):
1. ★ free -m 输出里的 free 那一列,意思不是
"还能用的内存",而是"完全没被【任何用途】碰过、
纯粹闲置的内存"。这是两个完全不同的概念。
2. 那消失的 9G,跑去了 buff/cache —— 是 Linux 内核
拿空闲内存,给【磁盘文件做的缓存(page cache)】。
3. ★ 关键:buff/cache 这块内存,是【可回收的】。
一旦有进程真的需要内存,内核会【立刻】把缓存
腾出来给它。它不是"被占死了",是"暂时借用"。
4. ★ 所以判断"还有多少内存能用",要看的是
available 那一列(它 = free + 可回收的缓存),
而【不是】 free 那一列。
5. 我的错:把 free 列当成了"可用内存",于是把
"Linux 拿闲内存做缓存"这件【好事】,错看成了
"内存被吃光了"这件坏事。
内存没"消失",它做了磁盘缓存。要看 available,不是 free。
修复 1:free 命令的每一列,到底是什么意思
# === ★ 把 free 输出的每一列彻底搞清楚 ===
$ free -h
total used free shared buff/cache available
Mem: 15Gi 6.2Gi 243Mi 180Mi 9.0Gi 8.8Gi
Swap: 2.0Gi 0B 2.0Gi
# === total:物理内存总量 ===
# 这台机器一共多少内存。没歧义。
# === ★ used:被进程"真正占用"的内存 ===
# 应用程序实实在在用掉的内存。这个数涨上去且不回落,
# 才值得警惕。
# === ★ free:纯粹闲置、没被任何用途碰过的内存 ===
# ★ 这是最容易被误解的一列。free 不是"可用内存"!
# 它是"连缓存都没拿去做、完完全全空着"的内存。
# ★ 在一台running 了一阵的 Linux 上,free 这个数
# 【天生就会很小】—— 因为内核会主动拿空闲内存去
# 做缓存(见修复 2)。free 小,是正常的、健康的。
# === ★ buff/cache:内核拿空闲内存做的缓存 ===
# 两部分:
# - buffers:块设备 IO 的缓冲。
# - cache(page cache):★ 缓存的磁盘【文件内容】。
# 你读过的文件,内核把它留在内存里,下次再读就
# 不用碰磁盘了 —— 这是 Linux 性能的一大来源。
# ★ 这块内存"用途"是缓存,但它【随时可以被回收】。
# === ★ available:这才是"真正还能用的内存" ===
# available ≈ free + buff/cache 里【可被回收】的部分。
# ★ 当一个进程申请内存:
# - 先用 free 的部分;
# - free 不够,内核【立刻回收 buff/cache】来满足它。
# 所以一个进程能拿到的内存,是 free 加上能回收的缓存
# —— 这个总和,就是 available。
# ★ 判断"内存够不够用",永远看 available,不看 free。
# === shared:多进程共享的内存 / tmpfs ===
# 共享内存段、tmpfs(比如 /dev/shm)占的。一般不大。
# === ★ 一句话总结这几列 ===
# free 小 + available 大 -> ★ 完全健康(缓存用得好)
# free 小 + available 也小 -> ★ 这才是真的内存紧张
# ★ 只看 free、不看 available,是这次踩坑的全部原因。
修复 2:为什么 buff/cache 高,是好事不是病
# === ★ 理解一句话:空闲的内存,是被浪费的内存 ===
# === Linux 的内存哲学 ===
# 内存买来就是用的。一块内存如果纯粹空着(在 free
# 列里),它对系统【没产生任何价值】—— 这叫浪费。
# ★ 所以 Linux 内核很"勤俭":只要有空闲内存,它就
# 主动拿去做 page cache,把磁盘上的文件内容缓存
# 起来。这样下次读同样的文件,直接从内存拿,快得
# 多 —— 内存的速度是磁盘的成千上万倍。
# ★ 这就是为什么一台跑了几天的 Linux,free 总是很小、
# buff/cache 总是很大 —— 内核在【物尽其用】。
# === ★ 它和"内存泄漏"的本质区别 ===
# 内存泄漏:进程申请了内存,用完【不还】,used 一路涨,
# 涨到顶就 OOM。这是病。
# buff/cache:内核拿闲内存做缓存,★ 但它【随叫随还】。
# 进程一申请,内核立刻把缓存丢掉、把内存让出来。
# 这不是占用,是"借用"——而且是会主动归还的借用。
# ★ 二者看着都让 free 变小,但一个是"赖着不还"、
# 一个是"借了随时还",性质天差地别。
# === ★ 亲手验证:缓存确实"随叫随还" ===
# 看一眼当前:
$ free -m | awk 'NR==2{print "free="$4" buff/cache="$6" available="$7}'
free=243 buff/cache=9240 available=9020
# 现在跑一个吃内存的程序,或者干脆手动让内核回收缓存:
$ sync && echo 1 > /proc/sys/vm/drop_caches # 仅演示用
$ free -m | awk 'NR==2{print "free="$4" buff/cache="$6}'
free=8800 buff/cache=520
# ★ 看!buff/cache 立刻从 9240 掉到 520,free 立刻
# 涨到 8800。—— 这 9G 缓存,内核说还就还了。
# 它从来就不是"丢失的内存"。
# === ★ 一个反面教训:别去抢内核的缓存 ===
# 我以前不懂,看 free 小就慌,会去手动 drop_caches
# "清理"内存。★ 这几乎总是【帮倒忙】:你把内核
# 辛辛苦苦攒的、热乎乎的缓存清掉了,接下来一段时间
# 所有文件读取都得重新去碰磁盘 —— 系统反而变慢。
# ★ 缓存不用你清。进程需要内存时内核自己会清。
# === 认知 ===
# ★ buff/cache 高,是 Linux 在好好干活的标志,不是
# 故障信号。看到它高,该高兴 —— 你的内存没闲着。
修复 3:怎样才算"真的内存不够了"
# === ★ 真正的内存告急,长什么样 ===
# === ★ 信号 1:available 持续走低 ===
# free 小,不用管。但 available 如果【一路下降、
# 逼近 0】—— 这才是真的危险:连可回收的缓存都
# 快没得回收了。
$ free -m | awk 'NR==2{print $7}' # 盯 available 这个数
# ★ 监控内存,告警阈值要设在 available 上,不是 free。
# === ★ 信号 2:used 持续上涨且不回落 ===
# used 是进程真正占的。如果它【只涨不跌】,典型是
# 某个进程在【漏内存】。结合趋势图看最准。
# === ★ 信号 3:swap 开始被大量使用 ===
$ free -m
Swap: 2047 1800 247
# ^^^^ ★ swap 用了 1.8G —— 物理内存
# 不够了,内核开始把内存往
# 磁盘上的 swap 挪。
# ★ swap 被持续、大量使用,是内存紧张的强信号
# (偶尔用一点点不要紧)。swap 在磁盘上,极慢,
# 一旦频繁 swap,系统会肉眼可见地卡。
# === ★ 信号 4:vmstat 的 si/so 不为 0 ===
$ vmstat 1 5
procs ---memory--- ---swap--- ...
r b free cache si so
2 0 243000 9240000 0 0 # ★ si/so 都是 0 —— 健康
5 3 12000 180000 4096 8192 # ★ si/so 在动 —— 正在换页,告急
# si = swap in,so = swap out。★ 它俩持续非 0,说明
# 内存已经不够、系统在拼命和磁盘倒腾内存。
# === ★ 信号 5:dmesg 里出现 OOM ===
$ dmesg -T | grep -i 'out of memory'
[...] Out of memory: Killed process 8901 (java) ...
# ★ 看到这个,说明内存已经真的耗尽过,内核动手
# 杀进程了。这是最严重的信号。
# === 一张速查:到底慌不慌 ===
# free 小,available 大,swap 0 -> ★ 健康,别慌
# available 一路降到很小 -> ★ 真紧张,要查
# swap 持续涨 / vmstat si so 非 0 -> ★ 已经不够了
# dmesg 有 OOM -> ★ 已经出过事了
# === 认知 ===
# ★ 判断内存够不够,看的是 available、swap、si/so、
# OOM 记录这一组指标 —— 唯独不该看 free 那一列。
修复 4:不同版本的 free,显示还不一样
# === ★ 一个容易再踩的坑:free 命令本身有"版本差异" ===
# === 老版本 free(CentOS 6 时代)===
# 老版本的 free,输出里有一行让无数人误解的东西:
$ free -m # 老版本
total used free shared buffers cached
Mem: 15885 15642 243 0 210 9030
-/+ buffers/cache: 6402 9483 # ★ 这一行!
Swap: 2047 0 2047
# ★ 老版本里:
# - 第一行的 used(15642)= 进程 + 缓存,大得吓人。
# - "-/+ buffers/cache" 这行的 free(9483)才是
# "扣掉缓存后真正可用的" —— 要看这一行。
# ★ 当年无数人只看第一行 used 就以为内存爆了 ——
# 和我这次犯的是同一个错。
# === ★ 新版本 free(CentOS 7+ / 现在)===
# 新版本把这事做对了:直接给出 available 列,
# 不用你自己去第二行找。
$ free -m # 新版本
total used free shared buff/cache available
Mem: 15885 6402 243 180 9240 9020
# ★ used 这里只算【进程真正用的】(6402),缓存
# 单独列在 buff/cache。available 直接告诉你可用量。
# ★ 新版本清楚多了 —— 记住认准 available 列就行。
# === ★ 配套命令,交叉印证 ===
$ cat /proc/meminfo | head -5
MemTotal: 16266240 kB
MemFree: 248000 kB # 对应 free 列
MemAvailable: 9236000 kB # ★ 对应 available 列
Buffers: 210000 kB
Cached: 9030000 kB
# ★ /proc/meminfo 是最原始的数据源,free 命令的
# 各列都是从这里算出来的。MemAvailable 就是
# available —— 内核自己估算的"可用内存"。
# === 看单个进程占多少内存 ===
$ cat /proc/8901/status | grep -E 'VmRSS|VmSize'
VmRSS: 2900000 kB # ★ RSS:实际占的物理内存(看这个)
VmSize: 12000000 kB # VmSize:虚拟内存地址空间(通常虚高)
# ★ 看进程"吃了多少内存",看 VmRSS,别看 VmSize。
# VmSize 包含了大量没真正落到物理内存的地址空间。
# === 认知 ===
# ★ 不管哪个版本的 free,你要找的永远是那个
# "扣掉可回收缓存之后、真正可用"的数 —— 老版本
# 在 "-/+ buffers/cache" 行,新版本就是 available 列。
修复 5:正确解法——看对指标,真泄漏才去查
# === ★ 解法:先看对数,再决定慌不慌、查不查 ===
# === ★ 解法 1:看 available,不看 free ===
$ free -m
# ★ free 小 + available 大 = 健康,什么都不用做。
# 这次"事故"的正确处理,其实就是:看一眼
# available 有 9G,然后【该干嘛干嘛】,根本没有
# 事故。所谓事故,纯粹是我看错了列。
# === ★ 解法 2:不要手动 drop_caches 去"优化" ===
# 网上有教程教你定期 echo 3 > /proc/sys/vm/drop_caches
# 来"释放内存"。★ 生产环境基本不要这么干:
# - 缓存被你清掉,系统接下来变慢。
# - 内核本来就会在需要时自动回收,根本不用你帮忙。
# ★ drop_caches 只在极少数场景有意义(比如做磁盘
# 性能基准测试,要排除缓存干扰)。日常运维别碰。
# === ★ 解法 3:确认是"真泄漏"后,定位漏的进程 ===
# 如果 available 真的在持续下降,按 RSS 排序找元凶:
$ ps aux --sort=-rss | head -10
# ★ 观察某个进程的 RSS 是不是【随时间只涨不跌】。
# 持续盯一段时间:
$ watch -n10 "ps -o pid,rss,comm -p 8901"
# RSS 一直爬升、从不回落 —— 这个进程在漏内存。
# === ★ 解法 4:真泄漏的根治在应用代码 ===
# 内存泄漏的根,几乎都在应用程序自己:
# - Java:堆设置 + 看 GC 日志,可能是对象一直被
# 强引用着回收不掉 -> 用 jmap / MAT 分析堆。
# - C/C++:malloc 了不 free -> valgrind 查。
# - 缓存类组件:本地缓存没设上限/淘汰策略,无限
# 往里塞 -> 给缓存加 maxSize、加过期。
# ★ 系统层面只能"发现"泄漏,根治得回到代码里。
# === ★ 解法 5:监控,阈值设在对的指标上 ===
# 内存告警,★ 阈值要设在 available 占比上,例如
# "available / total < 15% 才告警"。
# - 设在 free 上 -> ★ 天天误报(free 本来就小)。
# - 设在 available 上 -> 只在真紧张时才响。
# 同时监控:swap 使用量、vmstat si/so、OOM 日志。
# === ★ 解法 6:给内存留余量,别把 available 榨干 ===
# 就算 available 还有,也别想着"内存不用白不用",
# 把进程堆得满满的。★ 系统需要一定的缓存来维持
# 性能,也需要余量应对突发。一般让 available 长期
# 保持在 total 的 20% 以上,系统才从容。
# === 验证 ===
$ free -m # 认准 available 列
$ vmstat 1 3 # si/so 应为 0
$ dmesg -T | grep -i oom | tail # 不该有新的 OOM
# ★ available 充足、不 swap、无 OOM —— 内存就是健康的,
# free 列小到个位数也无所谓。
口诀放进脑子:free 小不是病,available 才是真可用。
修复 6:内存排查纪律
# === 这次事故暴露的认知盲区,定几条纪律 ===
# === 1. ★ free 列不是"可用内存",是"纯粹没被碰过的内存" ===
# === 2. ★ available 列才是"真正还能用的内存",判断够不够看它 ===
$ free -m # 认准 available
# === 3. ★ buff/cache 是内核拿闲内存做的磁盘缓存,可随时回收 ===
# === 4. buff/cache 高是好事,是 Linux 物尽其用,不是内存被吃光 ===
# === 5. ★ free 小 + available 大 = 健康;free 小 + available 也小 = 真紧张 ===
# === 6. ★ 真内存告急的信号:available 持续走低、swap 涨、vmstat si/so 非 0、OOM ===
$ vmstat 1 # 看 si so;dmesg | grep -i oom
# === 7. 别手动 drop_caches "优化内存",会清掉热缓存让系统变慢 ===
# === 8. ★ 老版本 free 看 "-/+ buffers/cache" 行,新版本直接看 available 列 ===
# === 9. 看进程占多少内存看 VmRSS,别看虚高的 VmSize ===
# === 10. 排查"内存好像快满了"的步骤链 ===
$ free -m # ① 看 available,不是 free
$ available 大 -> 健康,收工 # ② free 小是正常的
$ available 小 -> vmstat 看 si/so # ③ 确认是不是真紧张
$ ps aux --sort=-rss # ④ 真紧张就找漏内存的进程
$ 定位进程 -> 回代码查泄漏根因 # ⑤ 根治在应用层
# 按这个顺序,"内存快满了"基本能分清真假、能根治。
命令速查
需求 命令
=============================================================
看内存总览 free -h 或 free -m
看真正可用的内存 free -m (认准 available 那一列)
看内存最原始数据 cat /proc/meminfo
看缓存有多少 grep -E 'Cached|Buffers' /proc/meminfo
看是否在换页(内存告急) vmstat 1 (看 si so 列,非 0 就是告急)
看 swap 用了多少 free -m (看 Swap 那一行)
查 OOM 杀进程记录 dmesg -T | grep -i 'out of memory'
按内存占用排序看进程 ps aux --sort=-rss | head
看单个进程占多少物理内存 cat /proc//status | grep VmRSS
持续盯一个进程的内存 watch -n10 "ps -o pid,rss,comm -p "
手动回收缓存(一般别用) sync && echo 3 > /proc/sys/vm/drop_caches
口诀:free 列小不是病,那是内核拿闲内存做了磁盘缓存,buff/cache 高是好事可随时回收
判断内存够不够看 available 不看 free,真告急的信号是 swap 涨 vmstat si so 非 0 和 OOM
避坑清单
- free 命令输出的 free 那一列不是可用内存,它是纯粹没被任何用途碰过完全闲置的内存
- available 那一列才是真正还能用的内存,它等于 free 加上 buff/cache 里可回收的部分
- buff/cache 是内核拿空闲内存给磁盘文件做的页缓存,这块内存可以随时被回收不是被占死
- buff/cache 高是好事是 Linux 物尽其用,空闲的内存就是被浪费的内存,不是内存被吃光
- free 小加 available 大就是完全健康,free 小加 available 也小才是真的内存紧张
- 真内存告急的信号是 available 持续走低、swap 被大量使用、vmstat 的 si so 非 0、dmesg 有 OOM
- 别手动 echo 3 drop_caches 去优化内存,会清掉内核攒的热缓存让接下来所有文件读取变慢
- 老版本 free 要看 -/+ buffers/cache 那一行的 free,新版本直接看 available 列就行
- 看一个进程占多少内存看 VmRSS 实际物理内存,别看 VmSize 它包含大量虚拟地址空间虚高
- 内存告警阈值要设在 available 占比上不是 free 上,设在 free 上会因为 free 天生很小天天误报
总结
这次"内存好像快满了、却找不到是谁占的"的乌龙,纠正了我一个关于"空闲"的、根深蒂固的执念。在我的脑子里,内存这东西,只有两种状态:被某个进程占着,或者,空着。占着的,就是"已用";空着的,就是"可用"。这个二分法干净利落,我用了很多年,从没怀疑过。所以当 free 命令告诉我"free"这一列只剩两百多兆时,我的判断是瞬间完成、不容置疑的:可用的内存只剩两百多兆了,这台机器危在旦夕。我甚至没有多看一眼旁边那一列叫 available 的数字——在我的二分世界里,根本不需要第三个概念,"available"在我眼里只是"free"的一个同义词,一个多余的列。于是我一头扎进去找内存泄漏,可怎么算,所有进程加起来也填不平那个将近 10G 的窟窿。这个填不平的窟窿,本该是我最大的警觉:如果内存真的只有"进程占用"和"完全空闲"两种去向,那这 10G 不可能凭空消失。可我当时的反应不是怀疑自己的模型,而是怀疑这个世界——我觉得是有什么我看不见的东西在偷内存。复盘到根上,我才明白,我那个非黑即白的内存观,漏掉了一整个庞大的、它根本无法理解的中间地带。内存的去向,从来不是"被进程占着"和"空着"这么两种。还有第三种,而且往往是最大的一种:它被内核拿去,给磁盘上的文件做了缓存。这块叫 buff/cache 的内存,它处在一个我的二分法里没有位置的状态——它"被用着",因为它确实存着有用的数据,让文件读取快得飞起;可它同时又"是空闲的",因为任何一个进程只要开口要内存,内核会毫不犹豫、毫不拖延地把这块缓存丢掉,把内存让出去。它既是"已用",又是"可用"。我那把只有两个刻度的尺子,根本量不了这种东西。Linux 内核的逻辑,其实朴素得很:一块内存如果纯粹空着,它对系统就没产生任何价值,那才是真正的浪费。所以内核很勤俭,把一切暂时没人用的内存,统统拿去做缓存,让它发挥作用;而一旦真正的主人(进程)来要,它再立刻原物奉还。free 这一列之所以小,不是因为内存被吃光了,恰恰相反,是因为内核太会过日子,没让一块内存闲着。我把内核最值得称道的勤俭,误读成了系统最危急的灾情。那个我以为在偷内存的"隐形小偷",其实是个尽职尽责、随叫随到的管家。这次最大的收获,是我意识到,当一个系统的真实运作,比我脑子里的模型更精细的时候,我看到的所有"异常",很可能都不是系统的异常,而是我那个粗糙模型的"分辨率"不够。我的模型只有"占用"和"空闲"两档,而现实里有"占用"、"完全空闲"、"被借作缓存但随时可还"这至少三档——当现实里那第三档的数据摆到我面前时,我那台只有两档的"翻译机",只能把它强行归进某一类,于是翻译出了一个惊悚却完全错误的结论。问题从来不在那个数字,而在我那本残缺的字典。所以下一次,当一个指标给出一个让我惊慌的读数时,我会先压住那股慌乱,问自己一个更根本的问题:我现在用来理解这个读数的概念框架,它本身,真的足够描述这个系统的真实样子吗?会不会,这个系统里存在着一些状态,是我那套朴素的二分法,从来就没有给它们留过位置的?——很多时候,我们看到的"故障",只是现实的丰富程度,超出了我们头脑里那张简陋地图的承载能力而已。
—— 别看了 · 2026