
1. 先把剩余内存这个词拆开1.1 free 那一列小得吓人但不代表内存不够线上机器一报警很多人的第一反应就是敲free -h看 Linux 内存还剩多少。这个动作我做过不下几千次但真正把输出里每一列搞清楚的人不多。最常见的误判是看到free那一列只剩 200MB慌得直接重启机器其实那台机器离内存不够还差得远。Linux 和另一种主流桌面系统在内存使用哲学上差别很大。后者倾向于让内存尽量空着任务管理器里的可用数字接近真实空闲Linux 反过来只要有一块物理内存闲着内核就恨不得拿它去做页缓存page cache把磁盘上的文件内容提前搬进内存下次读同一个文件就不用再碰磁盘。这么做的直接后果是机器跑得越久free那一列越小哪怕这台机器上只跑着两个很轻的服务。用一个生活化的类比你的仓库有 100 个货位free表示此刻完全空着的货位buff/cache表示堆着货但随时能挪走的货位。仓库管理员发现货位空着就顺手把常用的货提前铺开放着走货更快。你进来一看过道全堆满了以为仓库爆仓其实只要有人来提新货管理员三分钟就能清出一条道。理解这一点后面所有的数字就都好读了。1.2 available 才是你要找的那个数较新的内核3.14 之后在/proc/meminfo里加了一个字段叫MemAvailable配套的新版procps-ngfree命令所属的软件包3.3.10 以上会在free输出里显示available列。这一列才是在不触发 swap 的前提下还能拿给新进程用的内存量的估算值。内核算这个数不是简单拿free加上buff/cache而是走了一套有依据的估算逻辑核心思路是available ≈ MemFree (Active(file) Inactive(file)) - min(该值/2, 低水位) (SReclaimable) - min(该值/2, 低水位)翻译成人话就是只有文件相关的缓存页和可回收的内核 slab才算数匿名页进程真正在用的堆栈内存一律不算同时还要扣掉一部分作为安全水位保护防止把缓存全吃掉之后系统立刻陷入危险。低水位wmark_low可以去/proc/zoneinfo里翻是内核给每个内存 zone 留的应急余量。所以判断一台机器内存够不够只看available这一列。free列可以低到几十 MB只要available还有几 GB这台机器就是健康的。1.3 buff/cache 里的水分有两类内存看着能回收其实不能新手最容易犯的第二个错是把buff/cache整个当成随时可以腾出来的内存。大体上没错但有两处水分必须扣掉。第一处是Shmem。tmpfs包括/dev/shm、/run挂在 tmpfs 的部分占用的内存会被算进Cached这一列里看上去像是文件缓存、可回收但 tmpfs 压根没有对应的磁盘文件除非系统配了 swap否则这些页面没法写回磁盘也就回收不了。我见过一个案例有人把大量中间数据写到/dev/shm里图省事结果buff/cache显示十几 GB 很漂亮真到了内存紧张的时候内核一点都回收不动直接被 OOM。判断方法很简单看/proc/meminfo里的Shmem字段这个数有多大Cached里就有多大的水分。第二处是正在回写的脏页。Dirty和Writeback字段不为零的时候对应的页面不能立刻丢弃得先落盘。Dirty长期偏高说明写回不及时这时候内存回收会卡在 IO 上表现为系统整体变卡、si/so飙升但available数字看着还行。这种数字健康但体验很差的情况只盯available是看不出来的得结合vmstat一起看。2. 四条命令覆盖 90% 的日常场景2.1 free -h第一眼该看哪一列日常排查free -h是使用频率最高的。它的输出长这样$ free -h total used free shared buff/cache available Mem: 62Gi 18Gi 1.2Gi 412Mi 43Gi 43Gi Swap: 8.0Gi 256Mi 7.7Gi各列含义和能不能算进可用内存的对照关系我整理成表格列名含义能否算进可用total物理内存总量通常比标称容量小一点分母不参与判断used已用内存等于 total - free - buff/cache参考free完全空闲的物理页能但它只是可用量的下限sharedtmpfs / 共享内存占用新版已移到 buff/cache 里基本不能除非有 swapbuff/cache块设备缓冲 页缓存大部分能但要扣掉 Shmem 和不可回收 slabavailable内核估算的可用量以它为准几个实用参数值得记住free -m/free -g按 MB / GB 显示看大数更直观。free -s 2 -c 5每 2 秒刷新一次共 5 次。判断内存是在涨还是在跌单次采样没意义必须看趋势。free -w把buffers和cache拆成两列单独显示需要 procps-ng 3.3.12 以上排查 IO 相关问题时有用。free -t最后多一行 total把内存和 swap 加起来。注意available列在老旧系统上可能不存在。CentOS 6 那一代机器内核 2.6.32的free就没有这一列得自己估算方法见 3.1 节。2.2 /proc/meminfo所有内存数字的源头free、top、vmstat这些命令显示的内存数据追根溯源全部来自/proc/meminfo。想搞清楚任何一个数字是怎么来的直接cat /proc/meminfo | head -n 20单位是 kB。常用字段我挑出来列一下都值得记字段说明MemTotal可用物理内存总量比标称小是因为内核镜像、硬件保留区、部分驱动先占了一部分MemFree完全空闲的页MemAvailable内核估算的可用量Buffers块设备元数据缓存通常不大Cached文件页缓存包含 ShmemShmemtmpfs 和共享内存Cached里的水分所在SReclaimable / SUnreclaim可回收 / 不可回收的内核 slabActive(anon/file)、Inactive(anon/file)四个 LRU 链表的页数available的计算基础Dirty / Writeback待写回 / 正在写回的脏页SwapTotal / SwapFree交换区总量和剩余AnonPages匿名页进程堆栈占的基本不可回收Mapped被 mmap 映射的文件页PageTables页表本身占的内存大内存机器上能到几个 GBCommitLimit / Committed_ASovercommit 机制下的承诺上限和已承诺量提取单个字段用awk最顺手注意$2是数值、$3永远是单位kB# 提取可用内存单位从 kB 转成 GB awk /^MemAvailable:/{printf %.2f GB\n, $2/1024/1024} /proc/meminfo顺手提一个容易忽略的项PageTables。每分配一页内存内核都要为它维护一条页表项。跑大量小内存进程比如几千个小容器、几万个线程的机器上页表本身的开销可能吃掉好几 GB。有些时候你发现所有进程的 RSS 加起来和used对不上差的那部分就在这里。2.3 top / ps把内存定位到具体进程知道还剩多少之后下一个问题永远是谁在吃。top进去按M键按内存排序是最快的路子。命令行一次性输出可以用psps -eo pid,ppid,user,rss,vsz,pmem,comm --sort-rss | head -n 11这里必须讲清楚top/ps里几个内存列的区别不然很容易误判VIRT / VSZ虚拟内存大小包含进程申请了但还没真正用到的地址空间包含共享库映射。这个数字大得离谱很正常Java 进程动不动几十 GB不代表真占了这么多物理内存。RES / RSS常驻内存进程当前真正在物理内存里的页。这是判断吃了多少内存的主要依据但它有个坑共享库、共享内存会被每个使用它的进程各算一遍。SHRRSS 里属于共享的部分。所以你把所有进程的 RSS 加起来结果远超物理内存总量这不是出错是共享页被重复计数了。想拿到相对准确的进程独占内存得用smem工具需要额外安装它基于/proc/pid/smaps计算 PSS按共享比例分摊加总起来才接近真实占用。单个进程想看细节直接读它的 status 和 smaps 汇总grep -E VmRSS|RssAnon|RssFile|RssShmem|VmSwap /proc/pid/status cat /proc/pid/smaps_rollupRssAnon匿名页和RssFile文件映射页分开看是判断这个进程是真吃内存还是只是映射了一堆文件的关键后面 4.2 节会展开。2.4 vmstat 和 slabtop看趋势和内核态占用单点采样只能说明现在判断内存压力要看趋势。vmstat是干这个的vmstat 1 10重点看两列si从 swap 换入和so换出到 swap。这两列持续非零说明内存真的紧张了内核已经在把匿名页往 swap 上倒腾。偶尔蹦出来一次不为零无所谓那是内核在做平衡连续十几个采样周期都是几百上千就说明物理内存不足以支撑当前工作集。free列在vmstat里也很直观但注意vmstat的buff和cache是分开的两列加起来对应free输出里的buff/cache。另一个常被忽略的方向是内核自己吃掉的内存slabtop -o -s c这条命令按对象大小排序显示内核 slab 缓存。dentry和inode这两项通常最大它们属于SReclaimable内存紧张时能自动回收不用管。但如果SUnreclaim那一列很大说明有内核模块在漏内存这种只能靠重启或者卸载模块解决应用层怎么优化都没用。我就遇到过一次某监控 agent 的内核模块持续申请 socket 相关 slab 不释放available一天掉一点最后是靠slabtop对比两天快照定位到的。3. 几个容易算错的指标和算法3.1 老系统没有 MemAvailable怎么自己估内核 3.14 之前的系统没有MemAvailablefree命令即使升级了也只能显示/- buffers/cache那两行老式输出。这种环境下的估算思路是把能回收的加起来再扣掉安全水位awk /^MemTotal:/ {t$2} /^MemFree:/ {f$2} /^Buffers:/ {b$2} /^Cached:/ {c$2} /^Shmem:/ {s$2} /^SReclaimable:/ {r$2} END { # 可回收页缓存扣除 Shmem再加上可回收 slab最后留 5% 作安全水位 est f b (c - s) r - t*0.05; if (est 0) est 0; printf 估算可用: %.0f MB (%.1f%%)\n, est/1024, est*100/t; } /proc/meminfo三个关键点解释一下为什么要这么写。一是Cached要减去Shmem。前面说过tmpfs 占的页算在Cached里但回收不了不减掉就会高估。二是要加SReclaimable。dentry、inode 这些缓存内存紧张时内核会自动收缩属于真正可用的部分。但要留意SReclaimable里也有回收不动的比如某些驱动注册的所以这里只是个上界估计。三是扣掉 5% 的安全水位。内核不会让可用内存真的归零每个内存 zone 都留了min/low/high三档水位。可以通过grep -A2 low /proc/zoneinfo看到具体数值再乘上 zone 数量。没耐心算的话按总内存 5% 估一般够用64GB 的机器留 3GB 左右比较接近内核的默认行为。3.2 swap 用了多少算安全很多运维规范里写着swap 使用率超过 50% 就告警这条规则我建议改掉。swap 被用了一部分本身是正常的内核把不常访问的匿名页换出去反而给页缓存腾了地方。真正要盯的是换入换出频率也就是si/so。一个简单的判断口径SwapFree少但si/so长期为 0没问题那些页只是躺在 swap 里没人碰。SwapFree还很多但si/so持续几百上千有麻烦说明工作集已经超过物理内存系统在反复换页磁盘 IO 会成为瓶颈。完全没有 swapavailable又很低一旦内存耗尽唯一的出路就是触发 OOM Killer 杀进程没有缓冲余地。vm.swappiness这个参数控制内核有多积极地换出匿名页。默认 60 对数据库类应用偏激进很多生产环境会调成 1 到 10让内核尽量保留匿名页、优先回收文件缓存。但它只影响倾向不改变总量物理内存真的不够时它救不了你。3.3 available 说够为什么分配还是失败这种情况我遇到过几次都是被available这个估计值误导了。原因大概有这四类。内存碎片。available是总量估计不代表有一块连续的大内存可用。要申请大页HugePage或者给网卡、GPU 做 DMA 映射的时候需要物理连续的内存块碎片一多就分配失败哪怕available显示还有几 GB。overcommit 策略。看/proc/sys/vm/overcommit_memory的值。设为 2 的时候内核严格按CommitLimit来审批Committed_AS接近CommitLimit就会拒绝malloc而这个拒绝跟available完全无关。数据库和 Redis 场景下经常要调这个参数调之前先算清楚CommitLimit swaptotal ram * overcommit_ratio / 100。cgroup 限制。容器里的进程看到的available是宿主机的但它实际能用的受 cgroup 内存上限约束。这时候free的数字完全没有参考价值得去读 cgroup 的计数文件。进程级限制。ulimit -v设了虚拟内存上限或者RLIMIT_AS被容器运行时限制了。这时候malloc失败返回 NULL程序报错但系统层面一切正常。提示排查分配失败但内存看着够的问题按这个顺序查——先看ulimit -v再看 cgroup 限制再看Committed_AS / CommitLimit最后才怀疑碎片。前三个都能一眼看出来碎片问题最麻烦。4. 实战三类内存不够的现场怎么查4.1 进程突然消失OOM Killer 复盘最典型的内存事故是某个进程毫无征兆地消失日志里只有一行莫名其妙的断开。八成是被 OOM Killer 杀了。查询路径dmesg -T | grep -i -E oom|killed process # systemd 系统也可以看内核日志 journalctl -k --since 1 hour ago | grep -i oom输出里会明确写出被杀进程的 PID、名字、占用的 RSS以及当时的内存快照total pagecache、free、slab各多少还会列出所有候选进程的oom_score。这份现场记录非常宝贵能直接告诉你事发瞬间是谁把内存吃满的。谁会被选中取决于oom_score而这个分数受/proc/pid/oom_score_adj影响旧内核里叫oom_adj。取值范围 -1000 到 1000越小的越不容易被杀。生产环境里给核心进程调低这个值是个常见做法echo -500 /proc/$(pidof mysqld)/oom_score_adj但我不建议一上来就靠这个保命。把关键进程保护起来代价是 OOM 时内核会去杀别的进程可能连 sshd 或者监控 agent 都被干掉机器直接失联。正确思路是先把内存问题的根因找到。还有一个容易忽略的点在容器里判断谁被杀了要看 cgroup 层面。容器内存超限时内核杀的是该 cgroup 里oom_score最高的进程dmesg里同样会有记录但看到的可能是宿主机视角的 PID跟容器内的 PID 对不上。排查时要结合容器运行时的事件日志一起看。4.2 内存只涨不跌这是泄漏还是缓存这是被问得最多的问题某个进程的 RSS 每天涨一点三个月后要重启一次到底是代码泄漏还是正常的缓存行为。判断方法其实很明确看RssAnon和RssFile的比例变化。# 采样两次间隔几小时对比匿名页的增长 awk /RssAnon|RssFile|RssShmem|VmRSS/{print} /proc/pid/statusRssFile增长基本可以放心那是文件页缓存被算进了进程的 RSS内存紧张时会自动丢。真正要警惕的是RssAnon持续单调增长——匿名页只增不减说明堆上分配了东西没释放这就是泄漏的特征。进一步定位用pmap看是哪一段地址空间在涨pmap -x pid | sort -k3 -n -r | head -n 20输出里[heap]段持续变大是最常见的泄漏信号。配合smaps能看得更细awk /^[0-9a-f]/{addr$1} /^Rss:/{if($210240) print addr, $2 kB} /proc/pid/smaps | sort -k2 -n -r | head这样能揪出超过 10MB 的具体内存段。对于 JVM 系的应用还要额外区分堆内和堆外。堆有上限看 GC 日志就知道是不是满真正容易失控的是堆外——DirectByteBuffer、Metaspace、JIT 编译缓存、线程栈。堆外内存泄漏的特征是堆用得不多但进程 RSS 一直涨-XX:MaxDirectMemorySize没设置的话默认等于堆大小很容易超。排查用Native Memory Trackingjava -XX:NativeMemoryTrackingsummary -jar app.jar jcmd pid VM.native_memory summaryRedis 也有类似的坑。fork做持久化的时候会触发写时复制COW如果这期间写入量大内存可能翻倍。所以maxmemory不能设到接近物理内存上限得留出至少一半余量或者干脆关掉自动重写、改用其他持久化策略。4.3 容器里的读数为啥全是错的在容器里敲free -h看到的是宿主机的内存数据不是容器的。原因是/proc/meminfo属于 procfs早期并没有做命名空间隔离容器共享宿主机的这份数据。这就导致一个很尴尬的局面容器内存限制 1GBfree显示宿主机还有 100GB 可用应用以为自己很宽裕放心大胆地申请内存然后被 cgroup 一记闷棍打死。正确的读法是看 cgroup 接口。cgroup v2 的路径在/sys/fs/cgroup/下cat /sys/fs/cgroup/memory.current # 当前使用量 cat /sys/fs/cgroup/memory.max # 上限无限制时显示 max cat /sys/fs/cgroup/memory.stat # 细分anon / file / slab 等cgroup v1 则是/sys/fs/cgroup/memory/memory.usage_in_bytes和memory.limit_in_bytes。memory.stat里的anon和file分得很清楚比/proc/meminfo更适合做容量判断。另外file那一项在 v2 里包含 page cache容器内存看起来超标很多时候就是它可以通过写入memory.reclaim主动回收试试或者调整memory.high做软限制。如果确实需要在容器里给出接近真实的free输出可以考虑挂载专门做 procfs 虚拟化的方案把meminfo按 cgroup 限制换算后再呈现这样应用不需要改代码就能拿到合理读数。这是运维层面的绕行方案不是内核行为。5. 常见问题速查与避坑清单5.1 问题速查表把日常遇到的情况和对应处理整理成表出问题直接对号入座现象优先查什么常见原因处理方向free 很低但系统正常available页缓存占了空闲内存不用管这是设计如此available 持续下降vmstat 1的 si/so工作集超过物理内存扩容或优化内存占用si/so 持续非零swap使用趋势已进入换页状态查进程 RSS 排行考虑扩容进程被无声杀掉dmesg -T | grep -i oomOOM Killer分析内存增长来源RSS 只涨不跌RssAnon变化趋势堆内存泄漏pmap/ NMT 定位代码available 够但 malloc 失败ulimit -v、cgroup 限制进程级或 cgroup 配额调整限制或 overcommitbuff/cache 高但回收不动Shmem、SUnreclaimtmpfs 占用、内核泄漏清理 tmpfs 或重启容器内 free 数字离谱memory.currentprocfs 未隔离读 cgroup 接口5.2 我踩过的几个坑别随手清缓存。echo 3 /proc/sys/vm/drop_caches这条命令网上流传很广有人说内存不够就清一下。我劝你别在生产环境用。它会一次性丢掉所有页缓存接下来一段时间所有文件读取都要回磁盘IO 直接打满业务响应时间会肉眼可见地恶化。这个操作只适合做性能测试时构造一致的初始条件跑完就恢复。别只看峰值告警。内存监控只看某个瞬时峰值意义不大available从 20GB 掉到 19GB 然后反弹属于正常波动从 20GB 花三天匀速掉到 2GB才是真问题。我现在的做法是用sar -r或者采集指标落库看一周的斜率。别把 RSS 加起来当总用量。前面提过共享库和共享内存会被重复计算几十个进程加起来超出物理内存是家常便饭。真要算进程维度上smem看 PSS。tmpfs 的容量要单独管。/dev/shm默认大小通常是物理内存的一半很多应用数据库的共享内存段、某些语言的并行运行时会往里写东西一不留神就吃掉几 GB。可以用df -h /dev/shm随时看占用。最后分享一个我在用的监控脚本逻辑很简单定期采样MemAvailable低于阈值就把内存占用前 10 的进程打出来。放在 crontab 里跑事后复盘时日志特别有用。#!/bin/bash # mem_check.sh —— 可用内存低于阈值时记录 TOP 进程 THRESHOLD15 # 可用内存百分比阈值 read -r TOTAL AVAIL (awk /^MemTotal:/{t$2} /^MemAvailable:/{a$2} END{print t, a} /proc/meminfo) PCT$(awk -v a$AVAIL -v t$TOTAL BEGIN{ if(t0){print 0} else {printf %.1f, a*100/t} }) echo $(date %F %T) total${TOTAL}kB avail${AVAIL}kB avail_pct${PCT}% if awk -v p$PCT -v th$THRESHOLD BEGIN{exit !(p th)}; then echo [WARN] available below ${THRESHOLD}% ps -eo pid,ppid,user,rss,pmem,comm --sort-rss | head -n 11 fi回到最开始那个问题。我现在判断一台 Linux 机器内存够不够逻辑已经简化成三步先free -h看available再vmstat 1 5看si/so然后ps --sort-rss看谁在吃。三步走完二十秒内基本能得出结论剩下的就是照着上面几个坑逐条排除了。