内存被吃光了?——一次 Linux 内存使用率排查复盘

监控告警内存使用率 95%,差点扩容,一个 free -h 发现绝大部分是 buff/cache。排查梳理:free 每一列的含义、为什么该看 available 而非 free、used 高不等于危险、RSS 与 VSZ 之别、swap 与 swappiness、page cache 与 drop_caches 的误区,以及怎么区分假告警与真泄漏。

2024 年的一个下午,监控突然弹出告警:一台核心服务器内存使用率 95%。我心头一紧——这要是冲到 100%,OOM Killer 一上来,服务就得被杀。我当时几乎已经在准备扩容工单了,顺手登上去敲了个 free -h,却愣住了:被算进"已用"的那一大块,绝大部分其实是 buff/cache,而真正反映"还能不能用"的 available 那一列,明明还剩着一大截。那一刻我才意识到,我对 Linux 内存的理解,一直停留在"used 高就是危险"这种想当然的层面。这次排查逼着我把 Linux 内存这套东西彻底理清了。本文复盘这次实战。

问题背景

环境:CentOS 7,32G 内存,跑着一个核心 Java 服务
事故现象(其实是"假事故"):
- 监控面板告警:内存使用率 95%,红得刺眼
- 第一反应:要 OOM 了,赶紧扩容
- 但服务本身一切正常,响应、吞吐都没异常

现场排查:
# 1. 先别急着扩容,看一眼 free
$ free -h
              total  used   free  shared buff/cache available
Mem:           31Gi  9.2Gi  0.8Gi  0.3Gi      21Gi      21Gi
Swap:         2.0Gi  0Gi    2.0Gi

# —— 看出问题了:
#   used 只有 9.2G,buff/cache 占了 21G
#   监控说的"95%",是把 buff/cache 也算成"已用"了
#   而 available 这一列写着 21G —— 真实可用还有 21G!

# 2. 确认 available 才是"还能给程序用多少"
# buff/cache 是内核拿空闲内存做的磁盘缓存,
# 程序要用时随时能回收,根本不是"被占死了"。

根因(其实没有"故障"):
监控面板用了一个过时的公式:
  使用率 = (total - free) / total
它把 buff/cache 算进了"已用",于是显示 95%。
正确的公式应该是:
  使用率 = (total - available) / total
按这个算,真实使用率只有 ~32%。
这台机器根本没有内存压力 —— 是监控口径错了。

修复 1:读懂 free 的每一列

# === free -h 的每一列,到底是什么意思 ===
$ free -h
              total  used  free  shared buff/cache available

# total      :物理内存总量
# used       :进程真正占用的内存(不含缓存)
# free       :完全没被碰过的内存
# shared     :tmpfs / 共享内存占的
# buff/cache :内核用空闲内存做的【磁盘缓存】★
# available  :估算"现在还能给新程序用多少" ★★

# === 最关键的两个认知 ===
# 1. free 很小,不代表内存紧张。
#    Linux 的哲学是"空闲内存是浪费",
#    它会主动拿空闲内存去缓存磁盘数据(buff/cache),
#    让下次读磁盘更快。所以 free 长期很小是【正常】的。
#
# 2. 真正该看的是 available,不是 free。
#    available = free + 可回收的那部分 buff/cache。
#    它才回答"程序还能再申请多少内存而不触发换页/OOM"。

# === buff 和 cache 的区别(了解即可)===
# buffers:块设备 I/O 的缓冲(元数据之类)
# cache  :文件内容的页缓存(page cache)
# 现代 free 把它俩合并成一列 buff/cache,不必纠结。

# === 看更细的内存分布 ===
$ cat /proc/meminfo
MemTotal:       32861688 kB
MemFree:          831232 kB
MemAvailable:   22359104 kB    # <- free 的 available 来自这里
Buffers:          524288 kB
Cached:         20120576 kB
# /proc/meminfo 是 free 的数据源,要刨根问底看这里。

修复 2:为什么 used 高不等于内存危险

# === 把这次的"假告警"彻底讲透 ===

# === 错误的使用率公式(很多老监控在用)===
# 使用率 = (total - free) / total
# 问题:它把 buff/cache 当成"已用"。
# 可 buff/cache 是【内核随时能还回来】的,
# 算它头上,等于冤枉了内核做的好事。

# === 正确的使用率公式 ===
# 使用率 = (total - available) / total
# available 已经扣掉了"可回收的缓存",
# 它代表的才是真实的内存压力。

# === 自己动手算一次,验证给监控看 ===
$ free | awk 'NR==2{printf "按 free 算:%.0f%%   按 available 算:%.0f%%\n", \
   ($2-$4)/$2*100, ($2-$7)/$2*100}'
# 第 1 个数会很吓人(把缓存算进去了),
# 第 2 个数才是真相。两个数差很大,就说明
# 你的内存"告警"多半是口径问题,不是真故障。

# === 怎么判断内存到底有没有真的紧张 ===
# 信号一:available 持续很低(比如 < 10% total)
$ free -h          # 反复看 available 那列
# 信号二:swap 在持续地、明显地被使用(见修复 4)
# 信号三:看 si/so —— 内存和 swap 之间在频繁换页
$ vmstat 1 5
# procs ----memory----   ---swap--  ...
#  r b   free buff cache   si   so
# si/so 长期不为 0 -> 内存真的不够,在拿 swap 顶
# si/so 一直是 0   -> 内存其实够用,别被 used 吓到

# === 最后的兜底信号:dmesg 里有没有 OOM ===
$ dmesg | grep -i 'killed process'
# 有 -> 内存真的爆过,要认真处理
# 没有 -> 大概率是虚惊一场

修复 3:到底是谁在吃内存

# === 确认要查"哪个进程吃内存"时,这样看 ===

# === 用 ps 按内存占用排序 ===
$ ps aux --sort=-%mem | head -10
# USER PID %CPU %MEM   VSZ    RSS  ... COMMAND
# --sort=-%mem:按内存百分比【倒序】
# 看前几行,内存大户一目了然

# === 必须搞清楚 RSS 和 VSZ 的区别 ===
# VSZ (虚拟内存):进程"申请"的地址空间总量,
#                 包含还没真正用上的、共享库等 ——
#                 这个数往往虚高,别拿它当真实占用。
# RSS (常驻内存):进程【真正占着物理内存】的量。★
#                 看一个进程吃了多少内存,看 RSS。

# === 看单个进程的内存细节 ===
$ cat /proc/3721/status | grep -E 'VmRSS|VmSwap'
VmRSS:   8123456 kB     # 常驻物理内存
VmSwap:    51200 kB     # 它有多少被换到 swap 了
# VmSwap 不为 0,说明这进程有一部分被挤到 swap 上了。

# === top 里看内存,按 M 键排序 ===
$ top
# 进入后按 大写 M -> 按内存排序
# RES 列 = RSS(常驻内存),这才是重点看的列
# VIRT 列 = VSZ,看看就行别当真

# === 一个坑:多个进程共享内存,RSS 会"重复计算" ===
# 比如多个 worker 进程共享一份代码段/共享库,
# 每个进程的 RSS 都把这份共享内存算了一遍。
# 想看"去重后的真实占用",用 smem:
$ smem -t -k -c "name rss pss"
# PSS(按比例分摊共享内存)比 RSS 更接近真实占用。

修复 4:swap——是缓冲垫,不是免费内存

# === swap 是什么 ===
# 物理内存不够时,内核把一部分"暂时用不到"的
# 内存页,挪到磁盘上的 swap 空间,腾出物理内存。
# 它是内存吃紧时的【缓冲垫】,但磁盘比内存慢几个
# 数量级 —— 一旦频繁用 swap,性能会断崖式下跌。

# === 看 swap 用量 ===
$ free -h | grep Swap
$ swapon --show          # 看 swap 分区/文件在哪、用了多少

# === 关键:swap 被用了一点,不等于有问题 ===
# 内核会把一些长期不活动的页"顺手"挪进 swap,
# 哪怕内存还够 —— 这由 swappiness 控制。
# 真正危险的是 swap 在【持续、大量】地换进换出(si/so)。
$ vmstat 1
# si(swap in)/so(swap out)持续很高 = 在"颠簸",
# 这才是 swap 带来的真问题。

# === swappiness:内核多积极地使用 swap ===
$ cat /proc/sys/vm/swappiness
60                       # 默认 60,0~100,越大越爱用 swap
# 服务器(尤其数据库)常调低它,让内核尽量留在物理内存:
$ sysctl vm.swappiness=10            # 临时
$ echo 'vm.swappiness=10' >> /etc/sysctl.conf   # 永久
$ sysctl -p

# === 该不该关 swap ===
# 别简单粗暴地关。没有 swap,内存一吃紧就直接 OOM,
# 连"挪一挪缓一缓"的余地都没有。
# 合理做法:配一块不大的 swap 当兜底,
# 同时把 swappiness 调低,让它平时基本不用、
# 只在真正吃紧时才顶上。

# === 给一台没有 swap 的机器临时加 swap 文件 ===
$ dd if=/dev/zero of=/swapfile bs=1M count=2048
$ chmod 600 /swapfile
$ mkswap /swapfile && swapon /swapfile
$ echo '/swapfile swap swap defaults 0 0' >> /etc/fstab

修复 5:page cache 的查看与回收

# === page cache:那 21G buff/cache 到底是什么 ===
# 你读过的文件,内核会把内容缓存在内存里(page cache),
# 下次再读同一个文件直接走内存,快得多。
# 它【不属于任何进程】,是内核全局管理的,
# 内存一紧张,内核会自动回收它 —— 所以它算"可用"。

# === 想看 cache 里缓存了哪些文件,可以用 ===
$ vmtouch /your/file        # 看某文件被缓存了多少(需装)

# === 手动回收 cache(★ 一般【不需要】,谨慎用)===
# 内核会自动管理,正常情况你【不该】手动 drop。
# 只有在排查、压测对照时才偶尔用:
$ sync                                  # 先把脏页刷盘
$ echo 1 > /proc/sys/vm/drop_caches      # 回收 page cache
$ echo 2 > /proc/sys/vm/drop_caches      # 回收 dentries/inodes
$ echo 3 > /proc/sys/vm/drop_caches      # 上面两者都回收
# 执行后再 free -h,会看到 buff/cache 掉下去、free 涨上来。
# ★ 但这只是把"有用的缓存"扔了,下次读盘又得重新缓存,
#   实际上反而拖慢系统。生产环境不要当例行操作。

# === 一个常见误解的澄清 ===
# 网上有人教"内存满了就 drop_caches 释放内存" ——
# 这是错的。buff/cache 本来就随时能被程序回收,
# 它"占着"不影响程序申请内存。手动 drop 纯属
# 自欺欺人,只会把性能搞差。
# 真内存不够,该查的是进程,不是去 drop 缓存。

修复 6:真内存泄漏怎么判断 + 监控该看什么

# === 区分"假告警"和"真泄漏" ===
# 假告警:used 高但稳定,available 充足,si/so 是 0
#         —— 多半是 buff/cache,不用管。
# 真泄漏:某进程 RSS 随时间【只涨不跌】,
#         哪怕业务量没变也一路爬升 —— 这才是泄漏。

# === 怎么抓"只涨不跌"的进程 ===
# 简单粗暴:定时记录目标进程的 RSS,看趋势
$ while true; do \
    date '+%H:%M:%S'; \
    ps -o rss= -p 3721 | awk '{print $1/1024 "MB"}'; \
    sleep 60; \
  done
# 跑几个小时,如果这个数一路单调上涨,就是泄漏。

# === 真定位到泄漏,按语言找工具 ===
# Java:jmap -histo / jcmd 看堆,dump 下来用 MAT 分析
# C/C++:valgrind --leak-check=full
# 通用:pmap -x PID 看进程的内存段分布
$ pmap -x 3721 | sort -k3 -rn | head
# 哪一段(堆 heap / 匿名映射)在异常增长,一目了然。

# === 监控:到底该盯哪个指标 ===
# ★ 内存使用率,必须用 (total - available)/total 这个口径。
# 用错口径(total-free),你会被 buff/cache 长期误报,
# 久而久之"狼来了",真出事时反而没人当真。
$ free | awk 'NR==2{print ($2-$7)/$2*100"%"}'   # 正确口径

# === 几个该一起看的辅助指标 ===
# - available 的【绝对值】和【趋势】
# - swap 的 si/so(vmstat)—— 持续不为 0 才是真信号
# - dmesg 里的 OOM 记录
# - 关键进程的 RSS 趋势
# 这几个一起看,才能分清"虚惊"和"真该处理"。

# === 内核的内存兜底:OOM Killer ===
$ dmesg -T | grep -i 'out of memory'
# 内存真的耗尽时,内核会挑一个进程杀掉自救。
# 想保护关键进程不被它选中,可调低其 oom_score_adj:
$ echo -800 > /proc/3721/oom_score_adj

命令速查

需求                        命令
=============================================================
看内存总览                  free -h
看真实使用率(正确口径)    free | awk 'NR==2{print ($2-$7)/$2*100"%"}'
看内存详细分布              cat /proc/meminfo
看换页是否颠簸              vmstat 1
按内存排序看进程            ps aux --sort=-%mem | head
top 里按内存排序            top 后按 M 键
看单进程内存细节            cat /proc/PID/status | grep Vm
看进程内存段分布            pmap -x PID
看 swap 用量                free -h | grep Swap / swapon --show
调整 swappiness             sysctl vm.swappiness=10
看 OOM 记录                 dmesg -T | grep -i 'out of memory'

口诀:别看 used 看 available -> vmstat 看 si/so
      -> 真紧张才查进程 RSS -> 只涨不跌才是泄漏

避坑清单

  1. free 里 used 高、free 低是 Linux 常态,空闲内存被拿去做缓存是好事
  2. 判断内存够不够看 available,不看 free,available 才含可回收的缓存
  3. 内存使用率要用 (total-available)/total,用 (total-free)/total 会长期误报
  4. buff/cache 是内核可随时回收的磁盘缓存,不该被算成"已用内存"
  5. 看进程吃多少内存看 RSS 不看 VSZ,VSZ 是虚拟地址空间往往虚高
  6. swap 被用一点很正常,si/so 持续大量换进换出才是真问题
  7. 别简单关掉 swap,留小块 swap 兜底并把 swappiness 调低更稳妥
  8. 不要把 drop_caches 当例行操作,它只是丢掉有用缓存反而拖慢系统
  9. 真内存泄漏的特征是某进程 RSS 只涨不跌,定时记录 RSS 看趋势可确认
  10. 多进程共享内存会让 RSS 重复计算,要看去重的真实占用用 PSS

总结

这次内存告警的排查,与其说是解决了一个故障,不如说是纠正了我一个根深蒂固的错误观念。出事的那一刻我几乎条件反射地认定"内存 95% 就是要 OOM 了",手都伸向扩容工单了,可一个 free -h 命令就把我拦了下来。这件事最大的价值,是逼着我去认真理解 Linux 到底是怎么看待和使用内存的——而它的内存哲学,和我过去那种"used 越低越安全"的朴素直觉,几乎是反着来的。Linux 的核心理念是:空闲的内存就是被浪费的内存。一台服务器如果有十几个 G 的内存一直空着没人用,在内核看来这是一种损失,所以它会主动把这些空闲内存拿去给磁盘上的文件做缓存,也就是 buff/cache。你读过的文件内容被留在内存里,下一次再读同一个文件就直接命中内存,省掉了慢得多的磁盘 I/O。这部分缓存对性能是实实在在的贡献,而且它有一个至关重要的性质:它不属于任何一个进程,是内核全局掌管的,一旦哪个程序真的需要内存,内核可以瞬间把这些缓存回收掉、把内存让出来。理解了这一点,这次"假告警"的来龙去脉就清楚了:监控面板用的是一个过时的公式,把内存使用率算成 (total - free) / total,这个公式的致命缺陷,就是它把 buff/cache 也一股脑算进了"已用"。可 buff/cache 明明是随时能还回来的,把它算成已用,等于把内核做的好事当成了罪状,于是面板上就显示出一个吓人的 95%。而真正该用的公式是 (total - available) / total,因为 available 这个值,内核在计算它的时候已经把"可回收的那部分缓存"加回去了,它回答的才是那个真正要紧的问题——现在还能再给新程序分配多少内存而不至于触发剧烈换页或 OOM。按这个正确口径一算,这台机器的真实使用率只有三成出头,它根本没有任何内存压力,所谓的"事故"从头到尾就是一个监控口径的错误。这个认知的转变让我立下了一条铁律:看内存,永远别盯着 free,要盯着 available;算使用率,永远用 total 减 available,绝不用 total 减 free。这次排查也让我把"内存到底有没有真的紧张"这件事,建立起了一套可靠的判断方法,而不再是凭 used 那个数字瞎猜。光看 used 高是没有意义的,我学会了去交叉验证好几个信号:第一,available 的绝对值是不是真的低、并且在持续走低;第二,也是最硬的一个信号,用 vmstat 去看 si 和 so 这两列,它们代表内存和 swap 之间换页的速率,如果它们长期不为零,说明系统在"颠簸",在反复地把内存往慢速的磁盘上挪,这才是内存真不够用的铁证;第三,去 dmesg 里翻一翻,有没有 OOM Killer 出手杀进程的记录。只有当这些信号一起指向"紧张"时,我才需要进一步去查到底是哪个进程在吃内存。而查进程的时候,我也澄清了另一个长期模糊的概念:看一个进程占了多少内存,要看 RSS,也就是它真正常驻在物理内存里的量,而不是 VSZ,VSZ 是进程申请的虚拟地址空间总量,里面包含大量还没真正用上的、以及和别的进程共享的部分,这个数往往虚高得离谱,拿它当真只会把自己吓一跳。最后,这次排查还顺手破除了我对 swap 和 drop_caches 的两个迷思。swap 不是免费的内存,它是磁盘上的一块缓冲垫,内存吃紧时拿来救急可以,但它慢得多,一旦频繁使用性能就会断崖式下跌——不过反过来,swap 被用了一点点也完全不必恐慌,内核本来就会顺手把一些长期不活动的页挪过去。至于网上流传的"内存满了就执行 drop_caches 释放内存",这次我彻底想明白了它是个彻头彻尾的误区:buff/cache 本来就能被程序随时回收,它"占着"丝毫不妨碍任何程序申请内存,你手动去 drop,无非是把一堆有用的、命中率很高的缓存白白扔掉,下次读盘还得重新缓存一遍,纯粹是花力气把系统搞慢。这次从一场虚惊出发,我最大的收获,是终于摆脱了"used 高就危险"这种望文生义的恐慌,转而建立起一套基于 available、si/so、进程 RSS 趋势的、真正站得住脚的内存判断体系——而这套体系教会我的第一件事就是:不要被一个数字吓到,先搞清楚这个数字到底在度量什么。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
Linux教程

磁盘没满 df 却报 100%:一次 Linux 磁盘空间排查复盘

2026-5-20 17:41:33

Linux教程

域名解析时灵时不灵:一次 Linux DNS 排查复盘

2026-5-20 17:47:18

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索