news 2026/10/17 4:38:09

Linux空闲内存构建:从指标解读到内核参数调优实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux空闲内存构建:从指标解读到内核参数调优实战

1. 这个项目到底在解决什么问题

前阵子在做一套内部存储系统的内存专项优化,被同事问到一个看似基础、其实很要命的问题:服务器明明配了 128GB 内存,free看第一行却只有几百 MB 的 free,这内存是不是不够用?

这个问题背后,藏着一个很多人没认真对待的工作方向——空闲内存的构建。它不是简单地把内存空出来吃灰,而是根据业务真实负载,有意识地在系统里规划、保留出一块可用内存资源,让系统面对突发流量时不至于因为内存分配不及时而卡顿、换页,甚至被 OOM 干掉。

这篇文章就把我从指标解读、内核参数调整到压测验证的完整过程拆开说清楚。不管你是做运维、后端开发、SRE 还是 DBA,只要你手上有需要长时间稳定运行的服务器,这套思路都能直接套用。

1.1 被误读的"空闲内存"

先说个最常见的误区。很多人判断内存紧不紧张,习惯性只看free命令第一行的 free 字段。看到 free 只有几十 MB,就紧张得不行,急着去清理缓存、杀进程。

其实这是把内存管理机制想简单了。

Linux 内核的设计哲学是"内存闲着就是浪费"。你刚读了一个文件,内核会把文件内容留在 page cache 里;你访问了一个目录,inode 和 dentry 会缓存在 Slab 里。这些内存从数值上看是被"占用了",但它们随时可以被回收、重新分配给其他用途。所以真正需要关注的是available这个字段,也就是"在不触发明显 swap 的前提下,还能给新程序分配多少内存"。

说个直观的类比:free 字段像是演唱会现场已经空出来的座位,而 available 是包括走廊、休息区在内可以临时加座的空间。你盯着已经划定的座位数没用,得看整个场馆还能塞下多少人,这才是关键。

所以,构建空闲内存的第一步,是先纠正所有人对内存指标的判断标准——不要被第一行的数字带偏,要建立以 available 为核心的内存健康度认知。

1.2 为什么空闲内存需要专门构建

既然内核本身有回收机制,为什么还要"构建"空闲内存?直接把业务跑起来,内核不就会自动管理吗?

理论上确实会,但实际运行中问题很多。

核心矛盾在于:内核回收内存是需要时间的。当某个进程突然申请一大块内存,而系统发现现有空闲页不够时,它会走内存回收路径,把 page cache 里的脏页写回磁盘、把匿名页 swap 出去。这段时间里,申请内存的进程可能会被阻塞,延迟从几十毫秒飙升到几百毫秒甚至秒级。对于高并发服务来说,这种延迟抖动往往是致命的。

更麻烦的是,某些应用申请内存时根本不会提前告知。比如 Java 应用在 YGC 或扩容时突然需要几十 GB 堆内存,数据库连接池在高峰期需要为大量新连接分配缓冲——这些场景如果之前没有预留足够空闲内存,系统就会在响应速度上栽跟头。

构建空闲内存,本质上是在内核默认的内存管理策略之上,叠加一层"水位管理"和"回收策略"的人工干预。目的不是禁止内核使用内存,而是确保在任何突发情况下,系统都还有一块可以快速分配出去的"缓冲区"。这和开车保持安全车距是一个道理——你不希望每次刹车都踩到 ABS 抱死。

1.3 哪些场景最需要这份空闲

不是所有服务器都需要大力构建空闲内存,但它对以下几类场景几乎属于刚需:

场景类型典型表现缺少空闲内存的后果
高并发 Web 服务流量瞬时上涨,连接数翻倍新连接建立慢,接口 P99 延迟飙升
数据库 / 缓存系统连接池扩展、索引加载、buffer pool 扩容查询变慢,主从同步延迟增大
数据分析批处理任务同一时刻多个 Spark/Flink 任务拉起Executor 申请内存失败,任务失败重试
容器节点Pod 数量快速增加Pod 启动被阻塞,节点 NotReady
虚拟化宿主机VM 批量迁移或启动内存锁定失败,虚拟机启动超时

如果你的服务器属于上面任意一类,那就值得把"构建空闲内存"当成一个正式项目来做,而不是让它听天由命。

2. 构建空闲内存的关键指标与调参细节

想构建空闲内存,先得把系统当前状态看清楚。我不建议一上来就改内核参数,那是本末倒置。正确顺序是:先读懂指标,再理解参数原理,最后才动手调。

2.1 先把内存指标读准

我最常用的命令组合是free -m配合cat /proc/meminfo。前者适合快速扫一眼,后者适合深挖细节。

free -m total used free shared buff/cache available Mem: 128441 21051 482 1803 106907 105672 Swap: 4095 0 4095

注意看上面这个输出。free 字段只有 482MB,旁边 buff/cache 占了 106GB,但 available 还有 105GB。这个系统其实非常健康,因为那 106GB 的 buff/cache 大部分都能在需要时立刻回收。

/proc/meminfo里我最常盯的几个字段:

字段含义判断要点
MemTotal物理内存总量基线值
MemFree完全空闲的物理页单独看没意义
MemAvailable估算可用内存核心指标,建议监控这个
Buffers块设备缓冲可回收
Cachedpage cache大部分可回收
SReclaimable可回收的 Slab 缓存(inode/dentry)可回收
SwapFreeswap 剩余空间如果业务禁止 swap,要重点看

提示:MemAvailable 是内核根据多种因素估算出来的,不是简单把 free 加 cache 相加。估算算法涉及内存水位、可回收页比重等,我们不需要自己重算,直接用内核给出的结果就行。

实际判断时,我一般看两个维度。一是 available 占总内存的百分比,低于 20% 就要警惕;二是在业务高峰时段观察 available 是否会跌到某个绝对阈值以下。这两个维度结合,比单看 free 靠谱得多。

2.2 核心内核参数以及调整原理

读懂指标之后,才轮到参数层面。构建空闲内存牵扯到的内核参数主要是这几个:

参数默认值作用构建空闲内存时常见调整
vm.swappiness60控制内核回收内存时,对匿名页和页缓存的倾向程度调整为 10~30,降低业务进程被换出的概率
vm.vfs_cache_pressure100控制内核回收 inode/dentry 等 VFS 缓存的速度可调为 150~200,加速回收 Slab 缓存
vm.min_free_kbytes动态计算强制预留的最小空闲内存水位调整为物理内存的 0.5%~1%
vm.overcommit_memory0控制内存超额分配策略保持 0,或在严格规划时设为 2
vm.drop_caches0手动清理缓存只在调试时使用,不作为日常手段

逐个说原理。

vm.swappiness可能是被误解最多的参数。它不等于"系统使用 swap 的阈值",而是影响内核在回收内存时更倾向于回收"匿名页"还是"页缓存"。数值越高,内核越积极把不常用的匿名页换到 swap 去;数值越低,内核越倾向于优先回收 page cache,保住进程内存。默认 60 在服务器场景下偏激进,很多业务进程的 heap 会被换到 swap,导致时延抖动。我在构建空闲内存时通常会调到 20 左右,既能避免 swap,又不会让 page cache 的回收变得迟钝。

vm.vfs_cache_pressure很多人不熟悉,但它对 Slab 内存高的问题非常有效。如果你用slabtop看到大量 dentry 和 inode 缓存占着好几个 GB,而且这些内存不大好回收,可以适当提高这个值。比如设成 150,内核回收 VFS 缓存的速度就会比默认快 50%。不过别贪心,设成 500 甚至更高会频繁回收这些缓存,导致文件访问性能下降。

vm.min_free_kbytes是构建空闲内存最直接的一招。它决定了内核在内存分配路径上强制保留的最低空闲页数量。这个值设得合理,可以避免系统在极端情况下出现"内存分配死锁"或"直接回收风暴"。但也不是越大越好——它每大一点,available 就实实在在少一点,因为这部分内存是系统锁定的,谁都不能用。

vm.overcommit_memory则决定了系统允许进程超额申请多少内存。默认的 0 是启发式策略,内核会根据当前空余内存和申请大小粗略判断。对于高确定性场景,我建议先保持默认,重点做好前三个参数,避免一下子改动太多导致应用启动失败。

2.3 一个关于参数计算的实际例子

光看参数定义不够,我来演示一遍实际怎么算。假设服务器物理内存是 64GB,我们想预留出足够的空闲水位。

先看min_free_kbytes。经验公式是最低空闲内存控制在物理内存的 0.5% 到 1% 之间。

64GB 换算成 KB:64 × 1024 × 1024 = 67108864 KB。取 1% 就是 67108864 × 0.01 = 671088 KB,约 655MB。取 0.8% 大约是 536870 KB,约 524MB。

考虑到这台机器跑的是长时间在线业务,我一般取 512MB 左右作为安全水位。注意,不要把这数值拍脑袋设成几 GB,否则你会把大量可用内存白白锁死。配置完成后,可以通过/proc/zoneinfo观察各 zone 的 min、low、high 水位变化,确认调整生效。

再看 swappiness。这要看业务特点。如果服务是网络应用,内存里放着热点数据,我建议设 20;如果业务跑的是批处理任务,换出一些不常用进程也无所谓,可以保留默认或调高一点。但构建空闲内存的核心目标是保住业务进程的响应速度,所以大多数场景我都会压到 20 附近。

至于 vfs_cache_pressure,我会先执行slabtop看当前 Slab 总量。如果 Slab 占用超过物理内存的 2%,再考虑调到 150;否则保持在 100 就好,没必要为了调参而调参。

注意:任何内核参数调整,都要在低峰期进行。改完之后观察一个完整业务周期,确认 available 和回收延迟都比调整前更稳定,再决定是否固化到配置文件里。

3. 从摸底到落地的完整实操流程

理论讲清楚了,下面是我的实操路径。整个过程我习惯分成四个阶段:摸底、参数落地、进程规划、压测验证。每个阶段都有可复现的命令和配置,你可以照着走一遍。

3.1 摸底:先把系统内存现状看清楚

动手改任何东西之前,先摸清家底。这一步不能省,否则你后面调参就是盲调。

基础摸底命令:

free -m cat /proc/meminfo | grep -E 'MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|SReclaimable' vmstat 1 5

这三个命令能快速告诉你当前内存分布和 swap 活动。如果vmstat里 si/so 持续非零,说明系统一直在换页,这是内存偏紧的直接信号。

接下来要看历史趋势,单看当前时刻不够:

sar -r 1 # 查看历史内存占用,需要 sysstat 包

sar -r能看到过去每天、每小时的内存使用记录。重点看两个信息:一是有没有内存用满的时间段,二是可用内存全天最低值是多少。这两个数据直接决定你要预留多少空闲内存。

还要看进程级别的内存占用,找出哪些进程在持续膨胀:

ps aux --sort=-rss | head -20

这里列出的前 20 个进程就是内存大户。注意看有没有某个进程的 RSS 持续增长、从不清降——这种就是潜在的内存泄漏,光靠构建空闲内存是兜不住的,得先处理。

摸完底,我会在表格里记下当前基线:

项目数值备注
总内存64GB物理内存
高峰 available 最低值6GB来自 sar -r
Slab 总量1.2GBSReclaimable + SUnreclaim
swap 使用率0%业务上要求尽量不用
最大进程 RSS32GB某数据库进程

有了这张表,后面预留多少空闲、参数怎么定,就有依据了。

3.2 参数落地:把"空闲"变成可控状态

根据摸底结果,我在 64GB 内存的服务器上这样配置内核参数。

创建配置文件:

cat /etc/sysctl.d/90-idle-memory.conf

文件内容:

vm.swappiness = 20 vm.vfs_cache_pressure = 150 vm.min_free_kbytes = 524288

说明一下每个值怎么来的。swappiness = 20是因为这台机器跑的是数据库服务,任何一点 swap 都会引起延迟波动,所以优先保住匿名页。vfs_cache_pressure = 150是因为摸底时看到 Slab 占了不少内存,而且服务热点文件也不多,可以放心加快 VFS 缓存回收。min_free_kbytes = 524288就是 512MB,对应前面说的 0.8% 水位。

加载配置:

sysctl --system

加载后立即验证:

sysctl vm.swappiness vm.vfs_cache_pressure vm.min_free_kbytes cat /proc/zoneinfo | grep -A5 'Node 0, zone Normal'

/proc/zoneinfo里的 min、low、high 三个水位会相应变化,说明配置生效了。

这里有个经验:sysctl 命令只对当前运行状态生效,重启后失效。所以要确保配置文件放在/etc/sysctl.d/下并在加载路径中。如果需要回滚,直接把配置文件删除,再执行一次sysctl --system就能恢复默认。

3.3 进程与容器层面的内存规划

内核参数只是第一层防线,进程和容器层面的规划同样重要。我见过很多系统内核参数调得挺漂亮,但业务进程一下子把内存全部吃光,最后照样出问题。

先讲进程层面。如果服务是用 systemd 托管的,可以在 unit 文件里加上内存限制:

[Service] MemoryHigh=8G MemoryMax=10G

MemoryHigh是软限制,超过后内核会积极回收该 cgroup 的内存;MemoryMax是硬限制,不允许超过。这两个值要根据服务的实际内存画像来定,不要拍脑袋。比如数据库进程平时占用 6GB,峰值 8GB,那 High 设 10GB、Max 设 12GB 才有意义,否则反而限制了业务扩展。

对于 Java 应用,-Xmx不要顶着物理内存设。给堆外内存、线程栈、元空间留出余地,这个余地就是空闲内存的一部分。我见过有人把-Xmx设成物理内存的 90%,结果 JVM 一扩张,系统直接 OOM。

容器层面同理。如果是 Kubernetes 或裸容器,通过 cgroup 限制内存:

docker run --memory=8g --memory-swap=8g my-app:latest

这里的--memory-swap设成和--memory一样,意味着禁止容器使用 swap。这样容器内存被限制在 8GB 以内,超出就 OOM,不会拖垮整个宿主机。

进程规划完成后,再次核对空闲内存的计算逻辑:物理内存减去所有进程可能占用的最大值,剩下的才是真正的空闲。这个值应该不小于你在内核层预留的水位。如果小于,就说明给进程的配额给多了,需要调整。

3.4 验证:模拟压力下观察指标变化

配置改完、进程限制设好,不能直接说"完事",必须压测验证。

我先用 stress 工具模拟内存压力:

yum install stress # 或 apt install stress stress --vm 2 --vm-bytes 8G --vm-hang 10s --timeout 60s

这条命令会拉起两个进程,各自申请 8GB 内存并保持 10 秒,持续 60 秒。执行期间,另开一个终端观察:

free -h vmstat 1 5 grep -E 'pgscan_direct|pgscan_kswapd|pswpin|pswpout' /proc/vmstat

重点看几个指标。

第一,available 是不是直线下降到接近预留水位后保持稳定。如果 available 直接清零,说明预留空间计算有误。

第二,pswpin/pswpout有没有大幅上涨。如果压力期间 swap 开始剧烈活动,说明swappiness或者进程配额没调好。

第三,pgscan_direct代表内存分配路径上的直接回收次数。这个数值如果急剧上升,说明系统频繁走直接回收路径,即内存不够用,进程申请内存被阻塞,这时要检查是不是预留的空间太小了。

除了 stress,我更推荐直接用业务流量压测。把线上真实请求复制到测试环境,流量逐步加大,观察 available 曲线的变化。比如正常 QPS 是 1000,压到 3000、5000 时,系统是否还能保持至少 512MB 的可用内存。如果 available 在压力下跌破预留水位,就要回头调大min_free_kbytes或者收紧进程内存配额。

验证通过的标准我总结为三条:压力期不出现明显 swap、available 不触底、内存分配延迟基本平稳。

4. 监控规则与常见问题排查

调参和压测只是项目的前半段。参数上线后,真正的挑战是持续观察和排查问题。我在这部分把日常巡检要盯的指标和踩过的坑一并整理出来。

4.1 日常巡检应该盯哪些指标

构建空闲内存不是一次性动作,它需要持续监控来确认状态没有漂移。我常用的监控项和告警阈值如下:

指标采集方式建议告警阈值
MemAvailablefree或cat /proc/meminfo低于总内存的 20% 或低于预留值 1.5 倍
swap 使用率free或vmstatsi/so使用率持续 > 0
内存回收事件/proc/vmstat的 pgscan_*pgscan_direct 明显增长
进程 RSS 排行ps aux --sort=-rss某进程 RSS 超过设定阈值
OOM 事件`dmesg -Tgrep -i oom`

如果要自动化,可以写一个简单的巡检脚本,放到 crontab 里定时执行:

#!/bin/bash # 空闲内存巡检 AVAILABLE=$(awk '/MemAvailable/{print $2}' /proc/meminfo) # 单位为 KB if [ "$AVAILABLE" -lt 1048576 ]; then echo "$(date) 警告: available内存低于1GB,当前${AVAILABLE}KB" >> /var/log/mem_check.log fi SWAP_USED=$(free -k | awk '/Swap:/{print $3}') if [ "$SWAP_USED" -gt 0 ]; then echo "$(date) 警告: swap已使用 ${SWAP_USED}KB" >> /var/log/mem_check.log fi

这个脚本的核心逻辑很简单:available 低于预设阈值就告警,swap 一旦使用就告警。不要嫌简单,很多时候内核参数的异常漂移就是靠这种基础巡检抓出来的。

另外,有条件的话把指标接入可视化监控系统,绘制 available 和 swap 的七日趋势图。空余内存的健康状态看单点数值没用,要看趋势线是不是平稳。如果 available 逐日下降,说明系统里有进程在持续吃掉内存,要马上排查。

4.2 高频问题与排查思路

下面这几个问题是我在实际操作中反复遇到、也帮别人排查过多次的,按出现频率排序。

第一,free 显示很小,但系统完全不卡,要不要处理?

不要处理。遇到这个问题的人,往往是把 free 当成了唯一指标。只要 available 充足、swap 没动、业务无延迟告警,这就是内核缓存策略的正常表现。缓存不是为了占地方,是为了提升性能,强行清掉反而会拖慢服务。

第二,cache 居高不下,总觉得碍眼,频繁清理缓存有用吗?

短期内有用,长期看是治标不治本。page cache 就是内核拿来加速文件读取的,它占得越多,说明你在读写的文件越多。如果业务规律就是频繁读取大量文件,cache 高反而是好事。真正要警惕的不是 cache 高,而是 cache 高且回收慢、回收时导致业务停顿。这种情况要查的是vfs_cache_pressure和磁盘 IO 能力,不是简单地清缓存。

第三,swappiness 都调到 20 了,为什么还在 swap?

这是最容易被误判的情况。要理解 swappiness 只是调整回收倾向,不是禁掉 swap。如果系统在瞬间收到大量内存申请,而 available 不足以满足,内核照样会走 swap 路径。排查时先看是谁在申请内存,用dmesg查有没有进程触发大规模内存分配,再检查 cgroup 内存限制是否被冲破。如果瞬时申请量确实巨大,需要做的是增加预留内存或限制进程内存上限,而不是继续调 swappiness。

第四,系统一直 OOM,但 dmesg 里看不到 OOM 进程?

这种情况通常发生在容器或 cgroup 场景里。宿主机没有 OOM,但某个 cgroup 触发了 OOM。排查步骤是先查 cgroup 事件:

systemd-cgls cat /sys/fs/cgroup/memory/memory.events

如果是容器平台,查看容器的事件日志。cgroup 的 OOM 记录里会明确告诉你哪个 cgroup 超限,而不是哪个进程。

第五,修改了参数,重启后全部恢复默认?

这就是没有把配置写进持久化文件导致的。sysctl -w只对当前运行内存有效,重启就丢失。正确做法是写入/etc/sysctl.d/下的 conf 文件,并确保文件名数字前缀在加载顺序里靠后,避免被默认配置覆盖。

4.3 关于"清理缓存"的正确认知

最后一定要单独说说清理缓存这件事,因为这是运维人员最容易操作失误的地方。

drop_caches的完整用法是:

sync echo 1 > /proc/sys/vm/drop_caches

其中echo 1清理 page cache,echo 2清理目录项和 inode,echo 3全部清理。

这组操作被很多人当成调优工具,每当看到 free 第一行很小就执行一次。但我要强调:drop_caches只是一个调试工具,不是日常运维手段。

为什么?因为 page cache 的作用是缓存你经常访问的文件内容。你把缓存清掉,下次读取同一个文件时,内核必须重新从磁盘加载,读盘延时瞬间放大,线上服务很容易出现慢查询或接口超时。我见过有人为了把 free 数值"修好看",天天在高峰期执行 drop_caches,结果业务 P99 延迟翻了好几倍。

如果确实需要在一台内存非常紧、你又不想重启服务的机器上临时腾空间,执行drop_caches之前必须满足两个前提:一是业务处于低峰期,二是这些缓存里的文件不是业务高频访问的热点文件。

构建空闲内存的正确方式永远是靠水位管理、回收策略和进程限制这套组合拳,而不是靠手动清缓存来"挤"内存。

这套项目做下来,我最大的体会是:很多人追着 free 第一行数字纠结,其实是在和内核的内存管理机制对抗,方向就错了。真正要做的是理解内核如何评估可用内存、如何回收内存,然后顺着它的逻辑给它设定边界。空闲内存不是靠清理出来的,是靠规划出来的——规划物理容量、规划进程配额、规划回收水位,把"万一不够用"的情况提前想清楚,系统才能在突发流量面前真的稳住。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 4:38:07

GLM-5开源权重部署实战:MoE架构、202K上下文与推理效率全解析

大概是从上周开始,开源社区讨论热度最高的一个话题,就是 GLM-5 权重正式放出。如果你只盯着“744B”这个参数看,其实看不出太多门道,但把 MoE 架构、202K 上下文、推理效率这些关键字拼在一起,事情就不一样了——“效率…

作者头像 李华
网站建设 2026/10/17 4:37:58

Flutter在OpenHarmony上的表单开发实战:以剧本杀组队App为例

1. 发起组队的表单需求与设计做到剧本杀组队App的实战第四篇,终于要碰表单了。前面几篇我们把项目骨架搭好,首页列表能刷出店家发布的剧本局,用户点击“发起组队”之后,需要填写一整套组队信息。这个页面就是所有组队功能的数据入…

作者头像 李华
网站建设 2026/10/17 4:37:14

C++文件上传下载实战:基于libcurl的工业级模块设计

简介:本资源是一套面向C中高级开发者与跨平台网络编程学习者的实战项目包,聚焦libcurl在HTTP文件上传与下载场景下的工程化应用,并配套Node.js服务端用于端到端功能验证。资源包含872个文件,主体为227个JavaScript服务端逻辑文件、…

作者头像 李华
网站建设 2026/10/17 4:36:40

用发邮件实操理解TCP/IP四层模型:从SMTP到以太网帧

1. 项目概述:用发邮件这件事,把TCP/IP四层模型真正“看见” 你有没有过这种感觉:学了半年网络原理,一到面试被问“打开一个网页时数据怎么跑的”,脑子就卡在“先DNS再HTTP”这半句上?更别说让同学发封邮件…

作者头像 李华
网站建设 2026/10/17 4:36:38

YOLOv11引入ASFF自适应特征融合:解决小目标与遮挡检测实战指南

做目标检测这几年,我养成了一个习惯:拿到一个新模型,先不看它在公开榜单上的数字,而是先拿手头那一批真实业务数据跑一轮。YOLOv11刚出来的时候,FPN-PAN的neck收敛快、部署省心,可一旦遇到富含小目标和遮挡…

作者头像 李华
网站建设 2026/10/17 4:36:22

GitHub Trending日榜深度解析:从看到到落地的筛选方法论

1. 先别急着收藏,GitHub 日榜到底在告诉你什么作为一个每天打开电脑第一件事就是刷 GitHub Trending 的老开发,我太清楚日榜这个东西的"副作用"了:收藏夹里堆了几百个仓库,真正用起来的不到五个。但说实话,日…

作者头像 李华