Linux 内存去哪儿了:free 的 used 为什么不能信

举报
yd_282927746 发表于 2026/11/10 08:28:49 2026/11/10
【摘要】 used 里混着大量可回收的东西,该看的是 available 和 PSI;匿名页不可回收、页缓存可以,slab 分可回收与不可回收;RSS 之和永远对不上 used 的四个原因;cgroup v2 的 memory.max/memory.events 怎么读,以及 OOM 打分与排查五步。

一台"内存吃满"的机器

监控报警:某台 8G 的服务器内存使用率 96%,持续半小时。登上去看:

$ free -h
               total        used        free      shared  buff/cache   available
Mem:           7.6Gi       6.9Gi       210Mi        12Mi       512Mi       330Mi
Swap:          2.0Gi       1.8Gi       200Mi

used 6.9G,available 只剩 330M,swap 也快用完了。但 ps aux --sort=-rss | head 排下来,最大的进程才 800M,前十个加起来不到 3G。剩下 4G 去哪了?

这就是 free 最容易误导人的地方:used 里面混着大量可以回收的东西,而 available 才是你真正能用的内存。反过来,有时候 used 看着不高,进程却一个个被 OOM 杀掉——因为限制不在整机,在 cgroup。

这篇把 Linux 内存的账算清楚:哪些能回收、哪些不能、cgroup 里的数字怎么读、以及内存问题的排查顺序。

一、free 的输出到底怎么读

$ free -m
               total        used        free      shared  buff/cache   available
Mem:            7807        6801         205          12         800         331

四个关键字段的关系:

used = total - free - buff/cache
available ≈ free + 大部分 buff/cache + 可回收的 slab
  • free:完全没被使用的内存。这个数字小是正常的——Linux 会主动用空闲内存做缓存,空闲内存等于浪费;
  • buff/cache:页缓存(page cache)+ 块设备缓存 + 可回收 slab。这部分大部分可以随时回收给应用;
  • available:内核估算的"在不触发 swap 的情况下,还能给新进程用多少"。这是唯一该看的内存余量指标;
  • used:已经分配出去的,但其中包含共享内存、tmpfs、以及暂时不可回收的部分。

所以上面那台机器真正的问题是:available 只有 330M,说明可回收的部分已经很少了,不是 buff/cache 撑的。

如果 used 高但 available 也高(比如 used 6G、available 3G),那说明大部分是可回收缓存,不必紧张。

二、内存去哪了:五个去处

① 匿名页(anonymous pages)—— 应用真正占用的

进程的堆、栈、malloc 出来的内存。这部分不能回收,只能 swap 出去。ps 里的 RSS 就是它的一部分(不含共享页)。

② 页缓存(page cache)—— 可回收

读过的文件、mmap 的文件都在这。它让第二次读同一个文件不用碰磁盘。可用内存紧张时内核会回收它,所以它不影响 available。

判断方法:/proc/meminfo 里的 Cached,或者

$ grep -E "^(Cached|Buffers|MemAvailable|AnonPages)" /proc/meminfo
MemAvailable:     339620 kB
Buffers:           20132 kB
Cached:           731004 kB
AnonPages:       6233488 kB

这里 AnonPages 6.2G、Cached 只有 700M —— 结论很清楚:内存全在应用手里,没有缓存可回收。这就是为什么会 OOM 而不是"把缓存让出来"。

③ slab —— 内核对象缓存,分可回收与不可回收

dentry / inode 缓存(可回收)、socket 缓冲区、task_struct 等(不可回收)。看:

$ slabtop -o -s c | head -5
$ grep -E "SReclaimable|SUnreclaim" /proc/meminfo

SUnreclaim 持续增长且不回落,通常是内核对象泄漏(比如文件描述符没关、连接没释放),这类问题重启才掉,得从代码查。

④ 共享内存 / tmpfs

shared 那列。/dev/shm、/tmp 挂成 tmpfs 时,写进去的文件占的是内存不是磁盘。一个常见事故:程序往 /tmp 写了几个 G 的临时文件,df -h /tmp 看着还有空间(tmpfs 上限是内存的一半),结果整机 OOM。

⑤ swap

swap 用满不等于故障,但它说明匿名页已经溢出到磁盘。Swap 行如果 used 持续增长,通常意味着匿名页总量超过了物理内存,或者有内存泄漏在慢慢吃掉。

三、RSS 加起来对不上 used,是正常的

常见困惑:所有进程 RSS 加起来 7G,但 free 显示 used 只有 5G;或者反过来。原因有四:

  1. 共享库被重复计算。每个链接 libc 的进程都会在 RSS 里算上 libc 的页,实际只有一份。Pss 才是按共享比例分摊后的值(/proc/<pid>/smaps_rollup);
  2. 页缓存不算进进程 RSS。文件读进内存后归在 Cached,不归任何进程;
  3. slab、内核栈、页表本身都不在进程 RSS 里;
  4. tmpfs 里的文件看起来在"文件系统"里,实际占内存。

所以"进程 RSS 总和 vs used"永远对不上,别试图凑平。要精确核对,用:

$ awk '/^(AnonPages|Cached|SReclaimable|SUnreclaim|Slab|PageTables|KernelStack|Shmem):/{print}' /proc/meminfo

把这几项加起来,才是真正的去处。

四、cgroup:真正杀你的那个限制

很多人遇到"内存明明还有,进程却被 Killed",答案在 cgroup。

容器(Docker/K8s)通过 cgroup 给进程组设内存上限。此时整机内存充足也没用,撞的是组的上限。cgroup v2 看这几个文件:

$ cat /sys/fs/cgroup/memory.current     # 当前用量
$ cat /sys/fs/cgroup/memory.max         # 上限(max 表示不限)
$ cat /sys/fs/cgroup/memory.stat        # 分项:anon / file / slab / kernel
$ cat /sys/fs/cgroup/memory.events      # 关键!oom 计数

memory.events 里 oom_kill 计数大于 0,就说明这个 cgroup 里确实发生过 OOM 杀进程。想确认是"整机 OOM"还是"cgroup OOM",看 dmesg -T | grep -i "oom":

Memory cgroup out of memory: Killed process 12345 (java) total-vm:...

注意 cgroup v2 里 memory.current 把 page cache 也算进去了。一个只读大量文件的容器,memory.current 会接近上限,但这不一定会 OOM——内核会先回收 file 页。如果它持续顶到上限并且 memory.events 里 high / max 计数一直涨,那才是真压力。

还有个坑:Java 进程看不到 cgroup 限制(JDK 8 早期版本、或者没启用容器感知)。-Xmx 按整机内存算,容器限额 2G 它敢申请 8G,于是被 OOM kill 得毫无征兆。JDK 10+ 默认开启 UseContainerSupport,老版本要手动加。

五、OOM killer 为什么杀了"看起来最无辜"的进程

内核按 /proc/<pid>/oom_score 打分,分数高的先杀。影响分数的因素:占用内存大小、进程运行时长、oom_score_adj 设置。

$ cat /proc/$(pgrep -f myservice)/oom_score
$ cat /proc/$(pgrep -f myservice)/oom_score_adj

oom_score_adj 范围 -1000 到 1000。设成 -1000 表示"永不杀我"(慎用,可能让系统进入死锁),设成 1000 表示"优先杀我"。

生产上的常见做法:

  • 关键守护进程(sshd、监控 agent)设 oom_score_adj = -500 左右,避免 OOM 后连不上去排查;
  • 批处理、可重启的任务设正数,让它优先被牺牲。

但不要指望调分数解决问题,它只决定"杀谁",不解决"内存为什么不够"。

六、排查顺序:从大到小,从粗到细

我遇到内存问题时的固定顺序:

第一步:确认是不是真压力。

free -h                       # 看 available,不看 used
cat /proc/pressure/memory     # PSI:some/full 的 avg10 持续 > 5 才算有压力

PSI(pressure stall information)比使用率准得多:full 高说明有进程真的卡在等内存上。内核 4.20+ 支持。

第二步:分清整机还是 cgroup。

cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.events
dmesg -T | grep -i oom | tail -5

第三步:看内存构成。

grep -E "^(AnonPages|Cached|SReclaimable|SUnreclaim|Slab|Shmem):" /proc/meminfo

AnonPages 大 → 应用占的,去查进程;SUnreclaim 大且持续增长 → 内核对象泄漏;Shmem 大 → 查 tmpfs。

第四步:找具体进程。

ps aux --sort=-rss | head -10
# 更准:按 PSS 排序
for p in /proc/[0-9]*; do awk '/^Pss:/{s+=$2} END{if(s>0) print s, "'"$p"'"}' $p/smaps_rollup 2>/dev/null; done | sort -rn | head -10

第五步:判断是缓慢泄漏还是突发。

  • 缓慢泄漏:观察 AnonPages 是否单调递增,配合 pmap -x <pid> 或堆 dump;
  • 突发:查最近的流量、批处理任务、是否有大文件读入内存。

七、几个反直觉的结论

used 高不等于有问题。 一台跑了半年的数据库,used 90%、available 低是正常的——它就该占着内存别让出去。判断标准是 PSI 和有没有 OOM。

buff/cache 高不需要"清理"。 网上那些 echo 3 > /proc/sys/vm/drop_caches 的"优化",除了让接下来的读变慢,没有任何好处。内核比你更清楚该回收什么。

swap 不是洪水猛兽。 少量 swap 反而能让冷页让出内存给热数据。真正的问题是频繁 swap in/out(vmstat 1 里 si/so 持续非零),那叫 thrashing,比 OOM 还糟——系统活着但什么都不干。

内存"泄漏"未必在应用。 见过好几次是文件描述符没关导致 dentry 缓存涨、连接没释放导致 socket 缓冲区堆积,应用自己的堆很干净。查的时候别只看堆。

小结

Linux 内存的账,核心就三句话:

  1. 看 available 和 PSI,不看 used;
  2. 分清楚匿名页(不可回收)和页缓存(可回收)——前者才是风险;
  3. 确认限制在哪一层:整机、cgroup、还是 JVM 自己的堆上限。

把这三句记住,绝大多数"内存吃满"的报警都能在五分钟内定位到具体哪一层,而不是上来就重启。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。