☰
Linux内存缓存清理实战:从free告警到drop_caches的运维指南
2026/10/13 7:22:26 网站建设 项目流程

最近在处理一台老服务器的性能问题时,我发现系统可用内存告急,但进程列表里却看不出哪个应用在吃内存。这种“内存去哪了”的悬案,多半和Linux的页面缓存(Page Cache)脱不了干系。今天就把这套排查和清理思路完整梳理一遍,也算是对自己踩坑经历的一次归档。

1. 内容整体设计与思路拆解

1.1 核心需求解析

服务器运行时间一长,free -h 看到的可用内存越来越少,但 top 里查进程内存占用却又都正常。这种现象十有八九是 Linux 内核把空闲内存用作了缓存。内核这么做本身是好事,因为读写过的文件数据会被暂存在内存里,下次访问就能直接从内存命中,速度能快好几个数量级。可问题是,当某个程序突然需要申请大块内存时,内核虽然有回收机制,但如果回收不及时或者回收能力有限,就会表现为“内存不足”或者系统卡顿。

所以本篇的核心需求可以拆成三个层面:

  • 第一,搞清楚系统内存到底用在哪里,区分“真的不够用”和“只是被缓存占着”;
  • 第二,掌握手工释放内存缓存的操作方法,知道什么时候该清、什么时候不能乱清;
  • 第三,学会在清理后观察系统表现,确认清理动作是否有效,同时避免因为误操作引发性能回退。

1.2 方案选型背后的取舍

我第一次遇到这种问题,直接就是一连串的 echo 3 > /proc/sys/vm/drop_caches,把页缓存、目录项缓存、inode 缓存全给清了一遍。当时确实立竿见影,内存蹭蹭往上涨,但代价也很明显——缓存命中率降到零,后续 IO 负载飙升,数据库查询明显变慢。后来才慢慢意识到,清理内存缓存不是简单地跑几条命令,而是要在“腾出空闲内存”和“保留热数据缓存”之间做权衡。

  • 如果只是临时运行大任务,那可以清得彻底一些;
  • 如果是生产环境跑着数据库或高频 Web 服务,清完缓存后的短暂性能波动可能比内存不足更难受。

所以方案设计的核心思路是:先准确判断内存状态,再决定是否清理,最后选择合理的清理等级。不是所有场景都适合一刀切,也不是清得越狠越好。

1.3 适用场景与目标读者

这篇内容主要面向:

  • 被 free 告警和 OOM 困扰的系统运维同学;
  • 自己搭服务器、跑应用,但对内核内存机制不太熟的开发者;
  • 以及想深入理解 /proc 内存文件、想搞明白缓存回收原理的 Linux 爱好者。

读完之后,你至少能回答这几个问题:内存都去哪了?怎么判断要不要手动清理?清理时选 1 还是选 2 还是选 3?清理完如何验证效果?

2. 内存缓存的原理与瓶颈剖析

2.1 理解 Linux 的“空闲内存”哲学

Linux 的设计哲学里有一条:内存闲着也是闲着,不如拿来缓存。你在系统里每读一个文件,内核都会把文件内容放进 Page Cache;每访问一个目录,目录项也会被缓存起来,这就是 dentry cache;文件属性信息则存在 inode cache 里。这些缓存在大大加速文件访问的同时,也让 free 命令输出里 available 和 free 的差距显得特别大。

来看一个典型的 free -h 输出:

$ free -h total used free shared buff/cache available Mem: 7.6G 2.1G 1.2G 145M 4.3G 5.1G Swap: 2.0G 0B 2.0G

这里 used 是 2.1G,free 只有 1.2G,看着像内存不够。但 buff/cache 有 4.3G,available 是 5.1G。available 是内核估算的“在不触发 swap 的情况下还能分给新进程的内存”,它考虑了缓存的可回收性。也就是说,系统其实并不缺内存,缺的只是“没被用于缓存的空闲内存”。

但这个逻辑在遇到突发内存申请时有隐患。万一某个进程瞬间申请大内存,内核回收缓存需要时间,极端情况下就把 OOM Killer 给招惹出来了。所以理解 available 和 free 的区别,是判断是否要手动清理的第一道分水岭。

2.2 buff/cache 的构成拆解

buff/cache 这个数字是一个集合,进一步拆开包括:

  • Page Cache:文件页缓存,最核心的部分,读文件、写文件都会用到;
  • dentry cache:目录项缓存,保存目录和文件名到 inode 的映射关系;
  • inode cache:文件元数据缓存;
  • Buffer Cache:早期的块设备缓冲,现在很多已经融入 Page Cache,但在一些块设备读写场景里仍有体现。

在 /proc/meminfo 里有对应的字段:

$ grep -E "^(Buffers|Cached|SReclaimable|Shmem)" /proc/meminfo Buffers: 110336 kB Cached: 3486764 kB SReclaimable: 420392 kB Shmem: 145408 kB

Cached 主要就是 Page Cache 的大小,SReclaimable 是内核中可回收的 slab 内存,Shmem 是 tmpfs 占用的内存。tmpfs 文件占用的内存实际上算在 Cached 里,但这部分没法通过 drop_caches 释放,因为你得先把文件删掉。

2.3 回收机制的触发条件

内核回收内存缓存主要靠两条路径:

  • 一条是内存压力触发。当系统可用内存低于某个水位线(watermark),kswapd 内核线程会开始回收页缓存、压缩内存碎片、换出匿名页;
  • 另一条是直接回收。进程申请内存时如果 kswapd 来不及回收,会同步进入直接内存回收路径,这时进程的申请延迟会显著增加。

手动执行 drop_caches 其实相当于绕过水位线机制,强制让内核立即丢弃缓存。但这不等于内核的自动回收失效,只是你给了它一个“现在就要”的信号。生产环境下如果频繁手动清缓存,反而会掩盖真实的自动回收问题。

3. 实战:三种场景下的内存缓存清理方案

3.1 查看当前内存和缓存状态

动手清理前,务必先确认现状。我常用的命令组合是:

# 以人类可读方式查看内存概览 free -h # 查看 /proc/meminfo 关键字段 grep -E "^(MemTotal|MemFree|MemAvailable|Buffers|Cached|Shmem)" /proc/meminfo # 查看当前缓存相关参数 cat /proc/sys/vm/drop_caches

/proc/sys/vm/drop_caches 默认值一般是 0。它的取值含义是:

值含义
0不清除任何缓存(默认)
1仅清除 Page Cache
2清除 dentry 和 inode 缓存
3同时清除 Page Cache、dentry 和 inode 缓存

如果 available 比 free 大好几个 G,说明缓存很充沛,根本不用清;只有当 available 已经接近物理内存总量、free 非常低、且系统开始出现换页或性能异常的时候,才需要考虑手动干预。

3.2 场景一:临时运行大任务前的缓存清理

假设你马上要在服务器上跑一个内存需求量很大的任务,比如编译大型项目、加载大规模数据集,而当前可用内存确实紧张。这时可以先做缓存清理,给新任务腾出空间。

第一步,先同步脏页,把尚未落盘的缓存数据写回磁盘。这一步很关键,否则直接丢缓存可能造成数据丢失:

sync

第二步,清理页缓存:

echo 1 > /proc/sys/vm/drop_caches

第三步,验证效果:

free -h

在这个场景里,我会先清 1,再看结果。如果内存还是不够,再考虑清 2 甚至清 3。因为页缓存是占内存的大头,先把大头处理掉,通常就够了。

3.3 场景二:生产环境的高效清理(保留热数据)

生产环境清缓存,最怕的就是误伤热数据。一个折中做法是只清 dentry 和 inode 缓存,不动页缓存。因为目录项和 inode 缓存占用的内存相对较少,清完之后的性能回退远小于清页缓存。

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

这样做的好处是,那些频繁读写的文件数据还留在 Page Cache 里,对数据库、Web 服务而言,热数据缓存不丢,性能波动很小。缺点也很明显——释放的内存总量有限。所以这个方法适合“数据库可用内存偏低但还没到危险线”的场合,不适合“内存彻底不够”的紧急情况。

还有一种更精细的做法,就是写一段小脚本,把 drop_caches 和阈值判断结合起来:

#! /bin/bash AVAILABLE=$(awk '/MemAvailable/{printf "%d", $2/1024}' /proc/meminfo) TOTAL=$(awk '/MemTotal/{printf "%d", $2/1024}' /proc/meminfo) THRESHOLD=$((TOTAL * 2 / 10)) if [ "$AVAILABLE" -lt "$THRESHOLD" ]; then sync echo 1 > /proc/sys/vm/drop_caches echo "$(date): memory available ${AVAILABLE}MB below threshold ${THRESHOLD}MB, cache dropped" >> /var/log/cache-clean.log fi

这段脚本的意思是:当可用内存低于总内存 20% 时,自动执行一次缓存清理。你可以用 crontab 把它挂到每 5 分钟跑一次,充当一道自动回收之外的补充保险。不过要记住,这只是治标手段,真正的问题还是得从应用和内核参数层面找。

3.4 场景三:彻底释放内存的强制清理

如果系统已经出现交换分区占用持续增长、free 几乎为零、甚至 OOM 事件开始出现,这时候就别手下留情了,直接清到最彻底:

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

清理完的瞬间,可用内存会明显上升。可以配合一下内存压缩开关的调整,让后续回收更积极:

# 提高内存压缩倾向,默认值通常是 20 echo 60 > /proc/sys/vm/vfs_cache_pressure

vfs_cache_pressure 控制内核回收 dentry/inode 缓存的倾向性。默认 100 表示内核倾向于保持缓存;数值越大,回收越积极。设成 60 到 80 之间,既能避免目录缓存被过早丢弃,又能在内存紧张时更快回收。

但有一点要特别强调:echo 3 绝不是万能解药。有些内存根本不在这些可回收缓存里——比如不可回收的 slab 内存、内核栈、每个进程自己的匿名页。如果 drop_caches 之后内存依然紧张,说明系统瓶颈在别处,得从进程和内核参数入手排查,而不是反复跑 echo 3。

4. 关键参数解析与性能影响分析

4.1 drop_caches 文件的内核实现逻辑

/proc/sys/vm/drop_caches 是一个伪文件,写入数值并不会真的立即释放内存,而是给内核一个回收提示。内核的收缩机制会遍历所有 zone 的 LRU 链表,把对应的页面移出缓存。

这里有个很多人不知道的细节:写入 drop_caches 后,free 命令显示的内存量是逐渐下降的,因为内核回收页面的过程是异步的,尤其在系统本来就忙碌的情况下,回收可能分好几轮才完成。我之前刚写完 echo 3,马上看 free 发现没变化,还以为命令没生效,其实是内核还在忙活。稍等 10 到 30 秒再观察比较稳。

4.2 不同参数的实际效果差异表

为了直观看出三种参数的差异,我用同一台压力测试机分别跑了一轮对比:

操作执行前 available执行后 available粗略释放量
echo 11.8G3.6G约 1.8G
echo 23.5G4.1G约 0.6G
echo 31.7G4.4G约 2.7G

echo 1 清的是文件内容缓存,释放效果最明显;echo 2 清的是目录和元数据缓存,占用小但影响文件访问路径;echo 3 是双管齐下。所以如果是普通文件服务器,echo 1 性价比最高;如果是 Web 服务器或代码仓库,目录项缓存也很重要,动 echo 2 要小心;只有在批量任务或者压测场景里,我才会用 echo 3 求一个清净。

4.3 缓存清理后为何性能反而下降

这是一个很反直觉的现象,但非常重要:清理缓存本身会降低后续性能。

清缓存前,进程读取一个文件,如果文件内容还在页缓存里,那么 read 操作直接从内存返回,快如闪电。清完缓存后,同一份文件必须从磁盘重新读入内存,磁盘 IO 延迟是内存延迟的好几个数量级,程序响应速度自然就掉下来了。对数据库这种重度随机读负载来说,清一次 Page Cache,最直接的后果就是查询延迟从毫秒级飙到几十毫秒级,直到热数据页重新被加载回缓存。

所以我的建议是,缓存清理只适合在几个场景里用:

  • 应用已经停止服务或即将重启,缓存留着也是浪费;
  • 准备运行与现有服务无关的大任务,需要优先保证新任务内存;
  • 系统即将 OOM,需要时间窗口去排查根治问题。

日常运行中,宁可让缓存占着,也不要手贱去清。内核的自动回收机制在绝大多数场景里都比手动干预靠谱。

5. 常见问题与排查技巧实录

5.1 问题一:为什么 free 显示可用内存很少但进程内存正常

有一次同事发来一台机器的截图,free 显示 available 才 1G 出头,但是 ps aux --sort=-%mem 看下来,top5 进程加起来也不到 2G,很奇怪。

排查过程是这样的:

# 先看是不是共享内存或 tmpfs 占了大头 grep Shmem /proc/meminfo # 再看 slab 缓存,不可回收的部分有多少 grep -E "^(SReclaimable|SUnreclaim)" /proc/meminfo

结果发现是 tmpfs 文件占用了几百 MB,加上不可回收的 slab 有 700 多 MB。tmpfs 的文件占用的内存会计入 Cached,但通过 drop_caches 释放不掉,只能删文件。SUnreclaim 有一部分是内核数据结构,同样无法回收,得靠内核模块或长时间运行后自动整理。

后来我又发现一个常见原因:某个进程大量读写小文件,生成了海量 inode 缓存。这些缓存看着小,数量一多就积少成多。解决思路是关掉不必要的文件访问监控、日志索引类服务,或者考虑给系统加内存,单纯清理缓存只能临时缓解。

5.2 问题二:执行 drop_caches 后缓存会自动重建吗

会,而且重建速度往往很快。如果你清完 1 分钟后看 free,发现 buff/cache 又涨回去了,这不是没清干净,而是系统在正常工作,持续的文件读写又把缓存填了回来。只要系统还在运行,数据被访问过,内核就会重新缓存。这是正常现象。

有一个细节值得关注:drop_caches 对“正在被写入脏页缓存”的影响很小,因为脏页必须先写回磁盘才能释放。如果你通过 echo 3 强制清理时,系统还挂着一批没写完的脏页,回收过程实际上会先触发一次 flush,等落盘后再释放。这就是为什么我强烈建议前面先跑 sync 的原因——把落盘动作提前,释放过程会更干脆。

5.3 问题三:清理缓存会不会影响正在运行的数据库

分两层说。

从数据安全角度,drop_caches 只回收干净页缓存和元数据缓存,不会把进程已经 mmap 的私有页清掉,更不会动数据库的事务日志和缓冲池。数据库自身的 Buffer Pool 是进程内存的一部分,跟内核页缓存是两码事,所以清内核缓存不会丢数据库数据。

从性能角度,如果数据库重度依赖操作系统文件缓存(比如某些小型数据库配置里 innodb_flush_method 设成默认值,文件读写走 Page Cache),那清掉内核页缓存意味着所有文件读取都得重新走磁盘,短时间内的延迟飙升不可避免。更麻烦的是,清完缓存后系统 IO 压力骤然加大,磁盘队列深度会涨一波,可能引发连锁性能问题。

建议的操作方式是:选择业务低峰期执行;清完后盯着 vmstat 和 iostat 观察几分钟,确认 IO 没有长时间持续高负载。

5.4 问题四:如何确认清理是否生效

推荐看三个指标,而不是单看 free:

# 1. 查看页缓存变化 grep -E "^(Cached|Buffers)" /proc/meminfo # 2. 观察系统交换情况,确认没在换页 vmstat 5 # 3. 看 Page Cache 命中情况,需要 perf 或 cachestat 工具 cachestat 1 5

cachestat 来自 perf-tools 工具集,安装后可以直观看到缓存命中率。如果清理前命中率 95% 以上,清理后跌到 60% 以下,说明热数据还没重新加载,这时候业务如果对性能敏感,可以考虑预加载关键数据文件来加速回温。

5.5 我的独家避坑清单

  • 不要在频繁读写的高峰期清缓存,宁可等业务峰谷;
  • 永远先 sync,再 echo,顺序不能反;
  • echo 的值尽量按需选择,一般不需要一上来就上 3;
  • 设置 vfs_cache_pressure 要考虑持久化,写进 /etc/sysctl.conf 才是永久生效;
  • 多留意 /proc/meminfo 里的 Shmem,tmpfs 类内存靠清理缓存是释放不了的;
  • 最重要的是,drop_caches 是应急手段,不是日常调优方案。如果服务器长期依赖手动清缓存才能维持运行,那要解决的是应用内存泄露、文件缓存策略或者物理内存扩容,而不是反复按压这个“刷新键”。

6. 从清理到预防:一套可落地的内存观察流程

解决问题不能只停留在“按一下按钮”。我现在的习惯是,给每台重要服务器都配一个简单的内存巡检脚本,每天记录内存状态,然后落到日志文件里。脚本大概长这样:

#! /bin/bash LOG_FILE="/var/log/memory_daily.log" { echo "====================" date "+%Y-%m-%d %H:%M:%S" free -h grep -E "^(MemTotal|MemFree|MemAvailable|Buffers|Cached|Shmem)" /proc/meminfo echo "Top 5 memory processes:" ps aux --sort=-%mem | head -6 } >> "$LOG_FILE"

用 cron 每天早上 8 点跑一次。这样一来,连续观察几天,你就能看出内存是不是持续被缓存吃满、有没有进程内存逐步走高、available 是不是长时间贴着红线。有了这个基线数据,你就能判断到底需不需要调整 vm.swappiness、vm.vfs_cache_pressure 这些内核参数,而不是等到收到告警才手忙脚乱去清缓存。

另外一个实用的预防手段是调整 swappiness。默认值 60 表示系统在内存可用率低于 60% 时就开始考虑把匿名页换到 swap。对服务器来说,这个值可以调到 10 甚至 0,让它更倾向于保留匿名页,减少不必要的交换。

sysctl vm.swappiness=10 echo "vm.swappiness = 10" >> /etc/sysctl.conf

但注意,swappiness 改小后,内存压力下内核更偏爱回收文件缓存而不是换出匿名页,这可能导致文件读性能下降。这又是一个需要结合业务形态去权衡的参数。

7. 扩展实操:结合 systemd 实现自动化缓存管理

如果你懒得每天手工敲命令,用 systemd 定时器做一个自动化缓存清理服务也是一个不错的选择。先写一个清理脚本,比如放在 /usr/local/bin/drop-cache.sh:

#! /bin/bash sync echo 1 > /proc/sys/vm/drop_caches

再创建一个 systemd service 文件 /etc/systemd/system/cache-clean.service:

[Unit] Description=Drop page cache when memory is low After=multi-user.target [Service] Type=oneshot ExecStart=/usr/local/bin/drop-cache.sh

再创建对应的 timer:

[Unit] Description=Run cache-clean every hour [Timer] OnCalendar=hourly Persistent=true [Install] WantedBy=timers.target

最后启用:

systemctl daemon-reload systemctl enable cache-clean.timer systemctl start cache-clean.timer

这时候系统每小时清一次页缓存。不过说实话,这种方案有点粗暴,我建议只在开发测试机上这么搞。生产环境还是用之前那种带内存阈值的脚本更稳妥,配合 systemd timer 同样可以实现定时检查、按需清理。

现在我的习惯是,先靠自动回收机制解决 90% 的问题,再用巡检脚本观察趋势,偶尔用一次手动清理处理特殊情况。这样既不会让内存缓存失控,也不会因为频繁清理拖累性能。希望这套从原理到实操的完整思路,能让你下次再看到“内存又满了”的告警时,心里有数,手上有招。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询