ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

free 命令详解:available、buff/cache 与容器内存排查

2026/10/1 3:50:18 拓冰建站 浏览量
free 命令详解:available、buff/cache 与容器内存排查 free 命令大概是每个接触 Linux 的人学会的第一批命令之一敲一下回车几列数字出来看起来简单得不像话。可真到了生产环境里我见过太多人在这几列数字上翻车有人盯着 free 那一列只有 800M慌慌张张重启了数据库有人看到 buff/cache 占了 20G以为内存被什么东西吃光了动手去清缓存还有人拿 top 里的 RES 一加发现加起来比物理内存还大怀疑系统在骗人。这些场景我全都亲身经历过也踩过其中的大部分坑。问题不在于命令本身而在于剩余内存这四个字在不同语境下指的是完全不同的东西内核视角的空闲页、应用可马上申请到的内存、容器配额里还能用的额度、以及监控系统上那个画出来的曲线。这篇文章我打算把这几层彻底拆开讲清楚——free 输出里每个数字怎么来的、available 和 free 差在哪、buffer/cache 到底算不算可用、容器里为什么看到的数字不对劲、以及真正接近内存耗尽时该按什么顺序去查。内容会偏向实操命令和参数都能直接拿去用适合刚接触 Linux 不久的朋友也适合已经能熟练敲命令、但一直没搞明白数字背后逻辑的运维和开发同学。1. 先搞清楚剩余这个词的几个不同含义1.1 内核眼里的空闲内存和我们眼里的不是一回事Linux 从设计之初就把空闲内存当成一种浪费。只要内存没被占用内核就会拿去做页缓存——也就是把磁盘上读过的文件内容缓存起来下次再读就不用碰磁盘了。这部分内存随时可以在有进程申请时被回收让出来所以它既被占用又可用。这就直接导致了一个结论free 那一列数值很小完全不代表系统内存紧张。真正代表还能不能再申请到内存的指标是 available。这个概念是内核在 3.14 版本附近正式引入并写进 /proc/meminfo 的目的是给监控工具一个更贴近真实感受的估计值。它大致等于当前完全空闲的页加上预计可以被回收的页缓存和可回收 slab再减去一部分内核预留的水位线。说人话就是available 回答的是如果现在有个程序要申请一大块内存大概能拿到多少而 free 回答的只是此刻完全没有用途的内存有多少。这两个数在负载正常的机器上差距可以非常大。我手上一台 32G 的机器跑着 Nginx 加几个 Java 服务free 常年只有 1G 出头available 却有 20G 上下。如果你只看 free这台机器看起来随时要挂看 available它其实很健康。搞不清楚这个区别是做内存排查时最常见的第一道坎。1.2 一个真实场景数字吓人但系统毫无问题前年有一次值班监控平台半夜报警说某台服务器可用内存低于 5%。我登上去敲 free -h输出是这样的$ free -h total used free shared buff/cache available Mem: 31Gi 9.8Gi 1.2Gi 412Mi 20Gi 20Gi Swap: 2.0Gi 256Mi 1.8Gifree 只剩 1.2Gi看起来确实濒危。但 available 写着 20Gi同时业务接口的响应时间、错误率全都在正常范围内。这台机器的实际状态是白天大量日志文件和静态资源被读取内核把 20G 内存都拿去做 buff/cache 了业务进程真正占用的匿名内存只有 9.8G 左右。内存一点问题都没有报警规则写得太粗糙而已。后来我帮他们改监控把告警条件从 free 改成了 available并且加了一个连续三分钟的持续时间判断。误报量一下子掉了九成。这件事让我印象很深指标选错比没有指标更麻烦因为它会消耗你对告警的信任。1.3 容器和宿主机看到的根本不是一个世界再往深一层如果你在容器里执行 free得到的其实是宿主机的内存总量和余量跟容器自己能用多少毫无关系。因为 free 读的是 /proc/meminfo而在多数容器实现里 /proc 并没有做完全隔离。一个被限制只能用 512M 内存的容器在它内部敲 free可能会看到宿主机有 128G 内存、还有 60G 可用然后放心地跑一个需要 4G 内存的任务接着被 OOM killer 干掉。容器场景要老老实实去读 cgroup 的文件。cgroup v1 看 /sys/fs/cgroup/memory/memory.limit_in_bytes 和 memory.usage_in_bytescgroup v2 看 /sys/fs/cgroup/memory.max 和 memory.current里面还有一份 memory.stat 能看到匿名页、页缓存、slab 各占多少。这几个文件才是容器内内存判断的真凭实据。明白这三层区别之后free 的每一列再来读就顺理成章了total 是物理内存总量减去内核自己预留的一小部分free 是纯空闲used 是 total 减掉 free 再减掉 buff/cache而 available 才是我日常最需要盯的那个数。2. free 命令的完整参数拆解与推荐用法2.1 单位、格式和那几个很少人用的开关free 默认按 KB 输出数字长到没法一眼读。生产上我基本只用两种free -h 给人看free -m 或者 free -g 给脚本看。脚本里尽量别用 -h因为它会根据数值大小自动切换单位解析起来很烦。另外新版 procps-ng 里 free 的输出单位其实用的是 2 的幂1024 进制和磁盘容量那种 1000 进制不是一回事对账的时候要留个心眼。几个值得记住的参数参数作用我什么时候用它-h人类可读单位手动排查第一眼扫-m / -g固定 MB / GB写脚本、做对比-w宽格式拆开 buffer 和 cache判断页缓存构成时-s N每 N 秒刷新一次盯紧内存变化趋势-c N配合 -s刷新 N 次后退出采样记录不刷屏-t增加一行 total快速看整体-l显示 low / high 内存明细只在部分架构有意义-w 这个参数很多人没用过它会把 buff/cache 拆成 buffers 和 cache 两列。buffers 主要对应块设备元数据cache 主要对应文件页缓存和 tmpfs 等。排查到底是谁把内存吃掉了时这个拆分很有价值。2.2 用采样代替单次快照这是最关键的一个习惯单次 free 只能告诉你此刻的状态判断不了趋势。真正有用的做法是采样# 每 2 秒采一次共采 30 次输出到文件留档 free -m -s 2 -c 30 /tmp/mem_trace_$(date %F_%H%M).log别小看这一条命令。内存是否在缓慢泄漏、是否有周期性尖峰只有连续数据才看得出来。我曾经排查过一个 Java 服务的问题单看 free 一切正常但用 -s 1 -c 300 采了五分钟之后发现 available 每 40 秒就会掉 300M 然后回升——对上了某个定时任务全量加载缓存的节奏。换成单次查看这个规律永远发现不了。采下来的数据也可以直接丢给 awk 做简单统计# 统计 available 的最小值快速判断这五分钟里最紧张到什么程度 free -m -s 1 -c 60 | awk /^Mem:/ {print $7} | sort -n | head -1注意-s 与 -c 的组合在老一些的发行版上行为略有差别某些版本 -c 不生效会一直刷。写进自动化脚本前先在目标机器上跑一次确认别直接复制到批量任务里。2.3 版本差异带来的输出不一致free 属于 procps 或 procps-ng 包不同发行版的输出列数和含义有小差别。老版本的 free比如 CentOS 6 时代输出是这样的total、used、free、shared、buffers、cached。没有 available 这一列也没有 buff/cache 合并列。如果你在网上搜到的教程和实际输出对不上多半是版本问题先free -V看一下版本号。比较省事的判断方法如果输出里有 available 列说明内核和 procps 都比较新直接用 available 做判断如果只有 buffers 和 cached 两列那就要自己心里算一下把 cached 里的大部分当成可用。升级 procps-ng 之后输出会自动变成新格式但升级前写死的解析脚本按列号取值的那些会全部错位这个坑我在一次批量升级里踩过几十台机器的监控采集直接断了一晚上。3. 钻进 /proc/meminfo把每个数字的来历弄明白3.1 核心字段的含义与相互换算关系free 本质上只是 /proc/meminfo 的一个格式化视图想真正读懂内存绕不开这个文件。直接 cat 出来有五十来行我挑日常最常用的几组说MemTotal可用物理内存总量。注意它比内存条标称容量小因为内核代码、数据结构、部分硬件保留区在启动时就被扣掉了。32G 的机器这里通常显示 31G 多一点。MemFree完全空闲的页。对应 free 命令的 free 列。MemAvailable内核估算的可分配内存就是 free 命令的 available 列。Buffers块设备读写相关的临时缓冲。Cached文件页缓存包括普通文件、tmpfs、共享内存等。这一项通常最大。SwapCached曾经被换出、现在又被换回内存但 swap 里副本还没删的页。这一项高说明系统在 swap 上来回折腾。Active / Inactive活跃和非活跃 LRU 链表大小。内核回收内存时优先从 Inactive 下手。AnonPages匿名页也就是进程堆、栈这些没有文件支撑的内存。这是真正被业务占着、不 swap 就回收不了的部分判断内存压力时比 used 更有参考价值。Slab / SReclaimable / SUnreclaim内核对象缓存。SReclaimable 是可回收的SUnreclaim 是回收不了的后者异常增长往往是内核模块或者驱动有问题的信号。Dirty / Writeback等待写回磁盘的脏页。突然飙高通常意味着磁盘 IO 跟不上间接会推高内存压力。Committed_AS / CommitLimit已经承诺出去的内存和承诺上限。这个和 overcommit 策略有关能解释一些明明还有内存却申请失败的现象。一个很实用的换算AnonPages 加上 SUnreclaim再加上各种内核栈、页表大致就是不可回收的内存。把这个数和 MemTotal 比一下就知道这台机器还有多少腾挪空间。3.2 MemAvailable 到底是怎么算出来的很多人以为 MemAvailable 是内核精确统计的其实它是个估算。内核源码里这个函数大致做的是这几件事/* 简化后的逻辑便于理解非源码原文 */ available 空闲页 - 内核预留页; pagecache 活跃文件页 非活跃文件页; pagecache - min(pagecache / 2, 低水位总和); available pagecache; /* 页缓存按大约一半计入 */ available 可回收slab - min(可回收slab/2, 低水位总和); if (available 0) available 0;拆开看有几个关键点。第一页缓存不是全额计入而是先减去一个和内存水位线相关的量剩下的再算进去大致上接近一半可以马上回收的经验值。第二可回收 slab 也只算了一部分。第三结果不会小于零。这解释了一个常见现象当你手动执行echo 3 /proc/sys/vm/drop_caches清缓存后free 那一列会大幅上涨但 available 的涨幅往往没那么夸张。因为 available 本来就已经把那部分缓存算进去了清不清缓存对它的影响本来就小。反过来说如果你观察到 available 在持续下降而 free 没怎么变说明是匿名内存业务实际占用在涨这才是真正需要警惕的方向。3.3 从 meminfo 走到 cgroup 限制容器里做内存判断正确的姿势是读 cgroup 文件而不是 meminfo。以 cgroup v2 为例# 容器内存上限max 表示不限 cat /sys/fs/cgroup/memory.max # 当前已用 cat /sys/fs/cgroup/memory.current # 详细构成anon / file / slab / kernel_stack 等 grep -E ^(anon|file|slab|kernel_stack|sock) /sys/fs/cgroup/memory.stat # 触发过多少次内存事件 cat /sys/fs/cgroup/memory.eventsmemory.current 减去 memory.stat 里 file 那一行页缓存可回收部分剩下的才是这个容器实际捏在手里的内存。很多 Java 容器设置的堆上限加上堆外内存超过 memory.max就会被 cgroup 层干掉而且 dmesg 里的 OOM 日志落在宿主机上容器内不一定看得到排查时容易一头雾水。提示容器内做内存告警时用 memory.current / memory.max 这个比值比用 free 的可用值靠谱得多。经验阈值设在 85% 左右配合 memory.events 里 oom 计数的变化一起看。4. 除了 free 之外还有哪些工具该在什么场合用4.1 top、vmstat、ps 三兄弟的分工free 给的是全局视角回答整机还剩多少。要知道谁在用就得换工具。top 里内存相关的几列含义差别很大VIRT 是虚拟地址空间大小基本没有参考价值Java 进程动辄显示几十 GRES 是常驻内存也就是真实落在物理内存里的部分SHR 是其中可能与其他进程共享的部分比如共享库。想按内存排序进 top 后按 ShiftM。注意 RES 之和会超过物理内存因为共享库和共享内存被重复计算了这不是 bug。vmstat 看的是内存和 IO 的联动。关键列是 si 和 so代表每秒从 swap 换入换出的内存量。只要这两个数长期为 0说明内存压力还没到需要动用 swap 的程度buff/cache 再高也不用慌。一旦 si/so 出现持续的非零值就说明系统真的缺内存了这比任何静态数字都更有说服力。另外 vmstat 里的 r 列运行队列长度如果明显大于 CPU 核数往往也和内存回收导致的卡顿有关。ps 用来给进程排个座次# 按 RSS 倒序取前 15 个进程 ps -eo pid,ppid,rss,vsz,cmd --sort-rss | head -15RSS 单位是 KB。想更精确地看某个进程的内存构成去看 /proc/PID/smaps_rollup里面区分了 Pss、Private_Dirty、Shared_Clean 等比 RSS 精细得多。需要按进程汇总共享内存的准确用量时用 smem 这个工具它给出的 PSS 和 USS 指标在容器计费和容量规划场景里更合适只是多数发行版要额外装。4.2 需要历史数据时用 sar 和 /proc/vmstat线上排查最尴尬的情况是出问题的时候你不在等你上去看系统早就恢复了。这时候静态工具全部失效得靠历史数据。sysstat 包里的 sar -r 能按天回溯内存使用# 查看今天的物理内存历史每 10 分钟一条 sar -r -f /var/log/sa/sa$(date %d) # 查看 swap 使用历史 sar -S前提是 sysstat 服务开着采集间隔合适。我个人建议内存相关的采集间隔不要超过 1 分钟否则短时间的尖峰会被平滑掉。/proc/vmstat 是另一个宝藏它记录的是累计计数配合两次采样的差值能算出很多细节。比如 pgscan_direct 和 pgsteal_direct 这两个计数器代表内核直接回收的扫描和回收页数。它们增长快说明内存分配已经开始走慢路径系统在为每一个新页付出回收代价——这是内存即将耗尽的早期信号比 available 变低更早出现。# 观察 10 秒内直接回收的次数变化 grep -E pgscan_direct|pgsteal_direct|oom_kill /proc/vmstat; sleep 10; grep -E pgscan_direct|pgsteal_direct|oom_kill /proc/vmstat4.3 几个容易混淆的内存指标该放哪热词里常出现的 jvm 内存模型、堆外内存、Spark 内存这些都属于应用层的内存概念和 Linux 系统层面的 free 不是一套坐标系。Java 进程的 RSS 通常等于堆内存-Xmx 以内实际用到的大小加 Metaspace 加线程栈加直接内存加 JNI 分配堆只占了其中一部分。所以看到一个 -Xmx2g 的 Java 进程 RSS 显示 4G不要急着判定内存泄漏先把它各段拆开看看。C 语言里的 union、栈内存溢出、Qt 应用的内存布局这类问题最终都会体现到 RSS 或者 AnonPages 上但排查手段完全不同前者靠代码审查和 valgrind、ASan 这类工具后者靠系统指标。搞清楚自己面对的是哪一层的内存能省下大量乱查的时间。5. 三个真实案例从数字异常到定位根因5.1 案例一available 持续下滑但 free 稳如泰山有台跑定时任务的机器每晚会跑一批数据加工。运维反映凌晨任务耗时会突然变长。我上去用采样命令记录了一整轮任务的内存变化free -m -s 5 -c 360 /tmp/overnight_mem.log结果很清晰任务开始时 available 是 18G随着任务推进一路降到 4G 左右任务结束后并没有回到 18G而是停在 12G 附近。free 那一列几乎没动——因为新增的内存全部是匿名页不体现在 free 上。再看 AnonPages 前后差值稳定在 6G 上下说明有东西在持续持有内存不释放。顺着这条线查进程 RSS最后定位到一个脚本里的临时数据全部加载进内存做排序且处理完没有及时释放引用。改成流式处理后内存曲线变成了平滑的锯齿形。这个案例的核心教训是判断内存趋势要看 available 和 AnonPages不要看 free。5.2 案例二free 显示内存充足却触发了 OOM另一台机器上free -h 显示 available 还有 6G但 dmesg 里出现了 OOM killer 的记录某个进程被杀。乍看不合理仔细查下去发现三层原因叠在一起第一层OOM killer 判断的是 zone 内的可分配内存不是全局 available。这台机器上有大页预留还有一部分内存被固定在 DMA32 区能分配给普通进程的额度比看起来的少。第二层被杀的进程申请的是连续的大块内存几 MB 级别的连续页而当时内存碎片化严重虽然有总量但没有连续块。/proc/buddyinfo 能直接看到各阶连续块的分布一眼就能确认。第三层vm.overcommit_memory 设置成了严格模式应用申请内存时被拒绝触发异常处理路径最后演变成 OOM。排查这类数字对不上的问题顺序很固定先看 dmesg 里 OOM 的完整堆栈和内存快照再看 /proc/buddyinfo 的碎片情况最后核对 sysctl 里的 overcommit 和 swappiness 参数。只看 free永远查不出来。5.3 案例三容器里数字正常容器外被限流一个客户反馈他们的服务偶发重启容器内看内存一切正常。我在容器内执行 free显示宿主机可用内存充足但把 cgroup 文件读出来一看memory.current 已经贴着 memory.max 了memory.events 里 oom 计数是 5。也就是说容器内的 free 完全看不到自己已经被限制的事实。进一步看 memory.statfile 那一项占了 60%这是页缓存。cgroup v2 在内存接近上限时会优先回收页缓存回收不动了就上 OOM。解决方法有两个方向要么把 memory.max 调大要么在应用侧减少对大文件的读取缓存依赖同时确认 Java 的 -Xmx 加上堆外预留之后没有超过容器上限。调整之后又加了一条监控规则采集 memory.current / memory.max 的比值超过 0.85 持续两分钟就告警。这比之前依赖应用内置的内存指标要灵敏得多。6. 内存查看的常见问题速查与避坑清单6.1 高频问题对照表现象大概率原因该怎么确认处理方向free 很低系统卡顿真的内存紧张或磁盘 IO 拖累看 vmstat 的 si/so、wa定位大内存进程评估扩容buff/cache 很高free 很低正常页缓存行为看 available 是否充足一般无需处理available 为 0 但服务正常估算偏保守或短时波动连续采样观察结合业务指标判断RSS 之和超过物理内存共享内存被重复统计对比 smaps_rollup属正常现象不用处理容器内看到宿主机内存/proc 未隔离读 cgroup 文件改监控数据来源swap 使用量持续上涨匿名页被换出看 vmstat 的 so 和 SwapCached查内存泄漏或调 swappiness内存充足却 OOM碎片化、cgroup 限制、overcommitbuddyinfo、dmesg、sysctl逐层排查别只看总量服务内存缓慢增长不下降内存泄漏长时间采样 RSS用堆分析工具定位6.2 几个我踩过的具体坑坑一把 drop_caches 当成常规手段。有些人一看到 cache 高就执行echo 3 /proc/sys/vm/drop_caches。这在生产上是个坏习惯清完之后所有文件都要重新从磁盘读短时间 IO 压力剧增性能反而下降。这种行为只适合临时测试绝不适合放进定时任务。坑二监控里用 used 做阈值。used total - free - buff/cache这个值在页缓存多的机器上会持续偏高导致大量误报。正确的做法是用 available或者用 AnonPages 这类只统计匿名内存的指标。坑三忽视 swappiness 的影响。vm.swappiness 控制内核有多愿意把匿名页换出去默认值 60 在服务器上往往偏高。数据库类应用通常建议设成 1 到 10减少不必要的换出。但也不能盲目设成 0完全关闭交换在某些突发场景下会把 OOM 提前引爆具体数值要看业务特性最好在测试环境验证。坑四内存压缩设置带来的误判。有些系统启用了 zram 或者 zswap把部分内存压缩存放。这时候 free 的 total 可能包含压缩池used 的含义也变得不那么直观看到 Swap 一行有数值不代表真的换了磁盘。排查前先确认/sys/module/zswap/parameters/enabled和 zram 设备状态。坑五解析脚本按列号取值。前面提过free 输出的列在不同版本会变。稳妥的做法是把 /proc/meminfo 当作数据源用字段名匹配而不是解析 free 的列位置。字段名在十几年里基本没变过稳定性高得多# 提取关键指标字段名匹配跨版本稳定 awk /^MemTotal:|^MemAvailable:|^AnonPages:|^SwapTotal:|^SwapFree:/ {printf %s %d MB\n, $1, $2/1024} /proc/meminfo6.3 一套可以直接抄的内存巡检脚本需求简单每分钟采一次只输出关键指标超过阈值打标记日志按天滚动。#!/bin/bash # mem_watch.sh —— 轻量内存巡检可放 crontab 每分钟执行 LOG/var/log/mem_watch_$(date %F).log AVAIL$(awk /^MemAvailable:/ {print int($2/1024)} /proc/meminfo) TOTAL$(awk /^MemTotal:/ {print int($2/1024)} /proc/meminfo) ANON$(awk /^AnonPages:/ {print int($2/1024)} /proc/meminfo) SWAPU$(awk /^SwapTotal:/ {t$2} /^SwapFree:/ {f$2} END {print int((t-f)/1024)} /proc/meminfo) RATIO$(( AVAIL * 100 / TOTAL )) FLAGOK [ $RATIO -lt 15 ] FLAGWARN [ $RATIO -lt 8 ] FLAGCRIT echo $(date %F %T) total${TOTAL}M avail${AVAIL}M anon${ANON}M swapused${SWAPU}M avail_pct${RATIO}% ${FLAG} $LOG # CRIT 时抓一份现场快照方便事后回溯 if [ $FLAG CRIT ]; then ps -eo pid,rss,cmd --sort-rss | head -20 $LOG fi这个脚本的价值在于快照留档。线上出事时最怕的就是我上去看的时候已经好了。有了这些按分钟记录的数据事后回溯轻松很多。阈值 15% 和 8% 是我在多数通用业务机上用的经验值数据库和缓存类机器要单独调整不建议一把尺子量到底。另外日志目录记得配合 logrotate不然一年下来也是几个 G 的文本。脚本本身可以再简化但CRIT 时抓进程快照这个动作强烈建议保留它救过我很多次。7. 我个人的几点经验内存这个方向我的体会是工具其实就那么几个难的是把数字和系统真实状态对应起来。这几年带新人我一般会先让他们做一件事拿一台测试机用 stress 之类的工具慢慢把内存压上去同时用 free -s 1 和 vmstat 1 盯着看观察 available、AnonPages、si/so 各自的反应。亲手看过一次变化过程比看十篇文档都管用。还有一点是关于告警的。内存告警不要只设一个阈值最好分三档available 占比低于 25% 提示、低于 15% 警告、低于 8% 紧急并且都加上持续时间条件。单点触发很容易被一次正常的批处理任务误伤。配合 AnonPages 的增长率一起看能提前几十分钟发现缓慢泄漏。最后一个习惯是任何一次内存问题的处理过程都留下记录——当时的 free 输出、vmstat 采样、cgroup 数据、最后的结论。攒够半年你就会发现大部分内存问题来来去去就那么几种模式下次遇到基本一眼就能认出来。