几乎每个刚接触 Linux 服务器的人都经历过同一个瞬间:打开 free -h 一看,内存已经用了 90%,吓得赶紧去找老板申请扩容。但资深的运维看了同一张表,却说"没问题,正常"。同样一张数字,为什么得出完全相反的结论?这篇文章想把这件事从底层讲清楚——搞懂了,你再也不会被这张表的数字骗到。
一、一个几乎所有新手都经历过的误判
服务器上跑了一段时间之后,你敲了这行命令:
free -h
输出大概是这样:
total used free shared buff/cache availableMem: 15Gi 12Gi 512Mi 234Mi 3.0Gi 2.8GiSwap: 2Gi 0B 2Gi
used 显示 12G,free 只剩 512M,换算一下,内存使用率已经超过了 90%。
如果你没深入研究过这张表,第一反应多半是:"内存快撑不住了,要出事了。"
但这个判断,很可能是错的。
二、先颠覆一个认知:Linux 不喜欢闲置内存
理解这张表,先要理解 Linux 内存管理的一个核心设计原则:
Free memory is wasted memory(闲置的内存是浪费的内存)。
这不是一句玩笑,而是 Linux 内核设计者非常认真的工程决策。
原因很简单:内存的读取速度远远快于磁盘(即使是 NVMe 固态,内存仍然快一到两个数量级)。Linux 内核会把读取过的文件内容、目录结构、程序代码等,缓存在空闲的内存里——这些缓存叫做 Page Cache(页面缓存)。
下次再访问同样的数据,直接从内存里取,速度快得多,磁盘 I/O 压力也小得多。
所以 Linux 会主动把几乎所有空闲内存都用来做缓存,这是有意为之的行为,不是内存泄漏,也不是哪个程序吃掉了内存。 看到内存使用率 80%、90%,不代表服务器"内存不够用了",只代表内核在努力地替你缓存数据。
三、重新读懂 free -h 这张表
理解了这个设计原则,再回来看这张表,每一列的含义就清晰了:
total used free shared buff/cache availableMem: 15Gi 12Gi 512Mi 234Mi 3.0Gi 2.8Gi
- used:被进程实际占用的内存(程序运行占用的堆、栈、代码段等)。
- free:完全空闲、什么都没放的内存,现代 Linux 上这个数字几乎永远很小,因为内核把大部分空闲内存都拿去做缓存了。
- buff/cache:内核用来做缓存的内存。这里包含两类:Buffer(文件系统元数据、块设备数据的缓冲)和 Cache(文件内容的页面缓存)。现代
free 命令把这两个合并显示成一列。关键是:这部分内存随时可以被回收——如果某个进程真的需要更多内存,内核会把这里的缓存丢掉、把空间让出来。 - available:这才是你真正应该看的数字。 它的含义是:当前系统里还能给新进程使用的内存量,等于
free 加上可以被回收的 buff/cache 部分。这个数字才真正回答了"系统还有多少内存可用"这个问题。
回到开头那张表:available 是 2.8Gi,说明系统还有接近 3GB 的内存可以随时调用,根本没到告警的程度。真正应该关注的是 available 是否持续下降并接近 0,而不是 used 有多高、free 有多低。
四、什么时候才是真的内存不够
了解了"缓存会占内存但随时可回收"之后,问题变成了:那内存到底什么时候才是真的不够用?
两个信号:
信号一:available 持续接近 0,同时 Swap 开始被使用
total used free shared buff/cache availableMem: 15Gi 14Gi 80Mi 234Mi 1.0Gi 150MiSwap: 2Gi 1.5Gi 512Mi
当 available 降到很低、同时 Swap used 开始增长,说明系统真的开始内存紧张了。
Swap 是划出一块磁盘空间,充当"内存的备用仓库"——当物理内存不够用时,内核会把一部分暂时用不到的内存数据"换出"到磁盘上的 Swap 区,腾出物理内存给更紧迫的进程用;需要时再"换入"回来。
Swap 的作用是防止系统在内存极度不足时直接崩溃,但代价很大:磁盘速度远低于内存,频繁使用 Swap 会让系统整体变慢,具体表现就是请求响应变慢、CPU 等待 I/O 升高。如果你发现 Swap 长期在使用且 available 持续很低,这才是真正需要加内存或者排查内存泄漏的信号。
信号二:OOM Killer 出手
当系统内存彻底耗尽、Swap 也用完,Linux 内核会启动最后一道防线——OOM Killer(Out of Memory Killer)。
OOM Killer 会按照一套算法,给系统里所有进程打一个 oom_score 分数,然后把分数最高的进程强制杀掉,释放内存,让系统继续运行。这个分数综合考虑进程占用的内存量、运行时间、是否是系统关键进程等因素——总体原则是:用内存多、运行时间短、不是系统关键组件的进程,最容易被选中。
OOM Killer 杀进程是有记录的,如果你发现某个服务莫名其妙消失了、日志突然断掉,但没有任何 crash 文件,很可能就是被 OOM Killer 干掉了。查验方式:
# 在 dmesg 里找 OOM 的记录dmesg | grep -i "out of memory"# 或者用 journalctl 查内核日志journalctl -k | grep -i "oom"
如果确认是 OOM Killer 动手了,接下来要做的是找出谁在消耗大量内存,而不是简单地把被杀的进程重启——重启之后还会再被杀,治标不治本。
五、怎么找出真正在大量消耗内存的进程
确认系统内存真的紧张之后,下一步是定位"谁在吃内存"。
方法一:top / htop
top
进入 top 界面后,按 M(大写)可以按内存占用从高到低排序,立刻看到内存占用最多的进程。
%MEM 列是进程占用的物理内存百分比,RES 列是实际使用的物理内存大小(Resident Set Size,常驻内存),这两个数字最直观。
方法二:ps 按内存排序
ps aux --sort=-%mem | head -10
这条命令列出内存占用最高的前 10 个进程,--sort=-%mem 表示按 %MEM 降序排列,一行命令直接给出结论。
方法三:从 /proc 读取进程的精细内存数据
前面讲目录结构的文章里提到,/proc/<PID>/ 下存放了每个进程的实时信息。内存相关的数据在 /proc/<PID>/status 里:
cat /proc/$(pgrep nginx)/status | grep -i vm
输出里有几个关键字段:
VmRSS:进程实际占用的物理内存(Resident Set Size)VmSize:进程申请的虚拟内存总量(通常远大于实际占用,不能只看这个)VmSwap:这个进程有多少数据被换到了 Swap 上
VmRSS 才是衡量"这个进程实际用了多少物理内存"的准确指标,而不是 VmSize——这两个值的差异,是新手排查内存问题时另一个经典的混淆点。
六、一个真实场景:内存泄漏的排查思路
内存泄漏是最难排查的内存问题之一——进程不会直接崩溃,但内存占用会随着时间缓慢、持续地增长,最终导致 OOM 或者服务响应变慢。
判断一个进程是否在泄漏内存,最简单的方式是:间隔一段时间,多次记录它的 VmRSS,看是否持续单调增长且没有回落。
# 每 5 秒采样一次 nginx 主进程的内存占用watch -n 5 "cat /proc/\$(pgrep -o nginx)/status | grep VmRSS"
如果 VmRSS 几个小时、几天内持续增长,而不是随着请求量波动(正常应该是有涨有落),基本可以确认是内存泄漏,需要进一步用 Valgrind、gperftools 等更专业的工具深入分析,或者联系开发排查代码逻辑。
七、关于 Swap:要不要关掉,要开多大
生产环境里,Swap 的设置方式常见的有两种争论:
观点一:关掉 Swap
很多 MySQL、Redis 的最佳实践文档建议直接关掉 Swap,原因是这些数据库对延迟很敏感,一旦数据被换出到磁盘,读取延迟会从微秒级跳升到毫秒级,对性能影响极大。在这种场景下,宁可让 OOM Killer 杀掉其他不重要的进程,也不愿意让数据库数据跑到 Swap 上去。
观点二:保留适当 Swap
另一种观点认为,完全没有 Swap 太危险——一旦内存真的耗尽,系统没有任何缓冲空间,直接 OOM 的后果更不可预测。保留一定量的 Swap 作为最后的缓冲,可以让系统在内存紧张时有更多时间给运维介入处理,而不是直接崩掉。
两种观点都有道理,要根据具体场景判断。但有一点是共识:Swap 是保底手段,不是正常工作状态,如果发现 Swap 长期在使用,应该把它当成需要解决的问题的信号,而不是"有 Swap 就没事了"。
总结:看内存,只看这几个数字
total used free shared buff/cache availableMem: 15Gi 12Gi 512Mi 234Mi 3.0Gi 2.8GiSwap: 2Gi 0B 2Gi
需要关注的:
available:持续下降、接近 0 → 真正的内存告警Swap used:开始增长 → 系统已经开始用磁盘补充内存,性能下降信号
不需要担心的:
used 很高 → 不代表内存不足,要看 availablebuff/cache 很大 → 好事,内核在高效利用空闲内存
Linux 的内存管理哲学归根结底是一句话:内核比你更懂得怎么用内存,它从来不会无缘无故地"浪费"内存,看起来"用完了"的内存,大部分时候是在帮你缓存数据。 真正需要你干预的,是 available 持续告急、或者 OOM Killer 开始出手的时刻。
你有没有遇到过"以为内存快满了,其实只是 cache 占用"的误判经历?或者你排查过真正的内存泄漏,最后是怎么定位出来的?欢迎在评论区聊聊。