先看一个常见现场:在服务器上跑 free -m,发现 free 还有不少,但 available 已经被压到几百 MB,应用频繁告警内存不足。这时候不要急着加内存,先确认 available 是怎么被扣掉的。
free 命令展示的 free 是当前真正没有被用到的物理页,而 available 是内核根据 MemAvailable 估算出来“还能安全分配给新进程”的内存余量。通常 available 会大于等于 free,因为内核会把可回收的 page cache 算进去。如果 free 多而 available 少,说明那部分空闲页没有被内核计入“可分配”范围,原因可能是被内存水位线、NUMA 节点、cgroup 限制或不可回收内存预留了。
先看内核估算 available 的取舍
内核在计算 available 时,会先扣掉各内存节点的 low 水位线保留内存,然后加上回收优先级高的 page cache,再减去不可回收的 slab 等。这里的 free 只反映空闲页数量,不反映这些页是否可以被任意分配。比如 min_free_kbytes 调得很大时,系统相当于人为预留了更多内存,free 可能不低,但 available 反而被压低。建议先执行 sysctl vm.min_free_kbytes 看当前值,再结合 free -k 观察。
这里要看你使用的发行版和内核版本,/proc/meminfo 中的 MemAvailable 字段在不同版本有不同算法,3.14 之前的内核可能没有这个字段。老版本 free 命令会自己估算,结果可能有偏差。
检查内存水位线保留页
内存分配器会为每个 zone 保留 min 水位线以下的内存,这部分主要用于紧急分配,普通用户申请不会动用。如果 vm.min_free_kbytes 被调得过高,系统的低水位线会跟着抬高,free 命令中还有大量 free 内存,但那些已经被“预占”了。验证方式是修改前先执行 free -g 保存基线,再修改后读取 /proc/zoneinfo 中各 zone 的 low,如果 low 值接近甚至高于 free,说明设置不合理。回滚时把原来的值写回即可,但要注意 sysctl 修改是即时的,有风险。
调整动作要谨慎:如果确实需要调,建议每次改完先观察 /proc/zoneinfo 中的 low 和 high 是否合理,同时观察 procps 版本对 available 的读数。这个值不是越大越好,调高后系统在内存压力下会更早进入回收,应用的可用余量会被压缩。一般先检查是否有人为设置过,再决定是否还原。
检查 NUMA 节点和内存区域分布
如果服务器有多颗 CPU,每个 NUMA 节点有独立内存,free 命令默认显示总和,可能隐藏节点间不平衡。例如节点 0 的 free 很少,节点 1 的 free 很多,但当前进程的内存策略只能从节点 0 分配,就会出现总量 free 很多、单节点可用紧张。此时用 numactl --hardware 查看节点内存大小,用 cat /proc/zoneinfo 查看每个 zone 的 present、free、high。
如果可用内存集中在 Movable 区域,而内核需要分配不可移动页,也会出现分配困难。可以看 /proc/buddyinfo 确认各 zone 的连续空闲块。注意 available 是对全系统整体估算,并不保证每个节点都有足够内存,因此多节点环境下看 available 很容易判断失误。遇到这种情况,需要结合进程的实际内存策略 numactl --show 来确认。
检查 cgroup 内存限制
容器或 systemd 服务场景经常把宿主机的 free 和 available 当成全局值,但应用实际能分配的内存受 cgroup 限制。例如 Docker 容器在 cgroup v2 下,限制路径是 /sys/fs/cgroup/memory.max,使用量是 memory.current;如果 memory.current 接近 memory.max,即使宿主机 available 充足,容器内也会触发内存回收或 OOM。而容器内执行 free 看到的是宿主机的 /proc/meminfo,并不反映 cgroup 限制。
检查顺序:先看是否属于容器或服务限制,再查 cgroup 的内存文件和 memory.events 中的 throttled 和 oom 字段。cgroup v1 下还要看 memory.limit_in_bytes 与 memory.oom_control 中的 oom_kill_disable。如果发现限制值很小,可以按业务需要调整。调整后需要重启容器或重新读取 memory.max,同时保留历史快照以便回滚。不要单纯因为宿主 free 多就盲目扩容器内存,要先确认 cgroup 当前使用量是否真的接近上限。
容易被忽略的不可回收内存
free 多、available 少,还有一个常见原因是不可回收内存偏高。打开 /proc/meminfo 看 SUnreclaim、PageTables、KernelStack、Reserved 几项。SUnreclaim 是不可回收的 slab 对象,比如某些内核模块、驱动缓存,它们不会因为内存压力而释放;PageTables 是页表占用的内存,进程多、内存映射多时也会变大。如果这些值异常增长,available 会被挤小,但 free 可能还有不少,因为内核并不认为这些内存可以回收。
排查时可以用 slabtop 看哪个 slab 占用量大,再用 dmesg 找有没有设备驱动报错或泄漏特征。这种问题通常需要结合内核版本和具体驱动确认,不能简单通过调整 vm.swappiness 或清 cache 解决。如果怀疑是临时性的,可以试着等后台回写完成,再确认 SReclaimable 是否降下来。
检查完后如果 available 仍低,可以写一个简单的监控脚本持续观察 MemAvailable 和 SUnreclaim 的变化趋势,判断是瞬时问题还是持续增长。如果应用已经出现分配失败,除了看 free 和 available,还要看 /var/log/messages 或 journalctl 中的 oom-killer 记录。每次 OOM 都会留下 affected 进程和内存统计,能辅助判断是节点限制还是 cgroup 限制。注意不要把 swap 的使用作为唯一依据,swap 增加只说明内存回收压力大,不能定位到根因。
最后可以这样说:查完以上几层,你会看到 available 少的原因通常不是单一因素,而是水位线保留、节点限制、cgroup 上限和不可回收内存共同作用的结果。每步检查都做好记录,改一处、验证一处,再考虑下一步。