☰
Linux内存buff/cache高?该不该清看available
2026/10/1 11:39:19 网站建设 项目流程

写这篇东西的起因,是很典型的运维场景。某天早上刚到工位,同事发过来一张监控截图,说新上的文件服务内存要爆了。我看了一眼,free -h输出里buff/cache占了十几个G,free那列只剩几百M,正常人的第一反应都是“内存不够了”。但再往下看一行,available还非常健康。类似的告警我处理过太多次了,大问题基本没有,小误会确实不少。所以今天把Linux下 buffer/cache 内存占用偏高这件事彻底说清楚:它到底是什么,什么时候该管,什么时候压根不用管,以及真正需要处理的时候应该怎么下手。

我在这一两年的日常排障里发现,很多人对缓存的理解停留在“缓存占内存 = 内存不够”的层面,一看到 cache 高就跑去清缓存,结果业务性能反而下降了。这篇内容主要给两类人看:一类是刚上手Linux服务器运维、看到free命令就心慌的新人;另一类是已经踩过 cache 清理坑、想知道背后原理和正确调优姿势的实践者。下面要讲的东西,我都会尽量按“为什么会这样”来拆解,而不是只给命令。

1. buffer/cache到底在“吃”什么内存

1.1 先搞清楚内核把内存花在哪了

Linux内核有一个非常朴素的理念:空闲内存放着不用就是浪费。你买回来的内存,要么给正在运行的进程用,要么给内核做各种缓存,唯独不应该长期躺着睡大觉。所以在Linux上,只要内存没有正经用途,内核就会把它拿去干“边角料”的活儿,这些活儿最终都归到了buff/cache这个统计项里。

具体来说,buff/cache里面大体装了三类东西:

  • Page Cache(页缓存):文件内容被读进内存后留下的副本。你cat过一个日志文件,grep过一个导出表,内核就会把对应磁盘数据留在内存里。下次再读同一份文件,直接命中内存,不用再走磁盘 I/O。这是所有文件读写性能的关键之一。
  • Buffer Cache(块缓存):历史上是用来缓存块设备原始数据的,比如磁盘、分区、文件系统元数据等。在现代内核里,buffer 和 page cache 在统计上经常合并展示,概念上可以把它们都理解为“内核在给磁盘I/O做加速的缓存区”。
  • Slab / dentry / inode 缓存:文件系统为了快速查找路径、解析文件属性,会把目录项(dentry)和索引节点(inode)也缓存在内存里。遍历过一个超大目录、批量创建过海量小文件,内存里就会积攒大量这类元数据缓存。它在free里归入buff/cache大项,具体看/proc/meminfo里的Slab字段。

这里有个很关键的点:上面这三类东西,绝大多数都是可回收(reclaimable)的。什么意思?就是当业务进程真的需要内存时,内核会把它们直接淘汰掉,把内存腾出来给业务用。你可以把 page cache 想象成办公桌上摊开的一堆文件夹副本,当你要放新文件、开新项目时,管理员会把副本收走,把桌面腾出来。真正的“占用”是指那种收不回来的占用,比如进程的匿名内存(堆、栈),那才是硬占。

1.2 一块能随时交还的内存,本质上不算“占用”

所以这里就引出一个特别反直觉的结论:free命令里的free列低,不代表内存不够用。因为真正的内存余量要看available,而不是free。

我之前遇到过一位刚做运维的朋友,盯着监控面板上的“已使用内存”发愁,非说某台机器内存告警。我让他跑了一遍free -h,再对照/proc/meminfo里的MemAvailable,他很快就意识到自己虚惊一场了。这个误区太普遍了,我甚至觉得它已经成了Linux新手入门的第一道坎。

内核之所以鼓励“吃了吐、吐了吃”的缓存策略,本质是为了提升系统整体吞吐。进程读文件时产生磁盘I/O,是最慢的操作之一,如果每次读同一个文件都要重新从硬盘搬数据,那服务性能会惨不忍睹。缓存就是拿空间换时间。所以buff/cache偏高,在绝大多数场景下是系统健康、资源被合理利用的表现,而不是故障信号。

2. free命令的正确打开方式

2.1 一份典型的free输出该怎么读

先来看一份典型的输出:

$ free -h total used free shared buff/cache available Mem: 15Gi 2.1Gi 1.2Gi 128Mi 12Gi 12Gi Swap: 2.0Gi 0B 2.0Gi

很多人第一眼会慌:used才2.1G,free才1.2G,总共15G内存,怎么只有1.2G没用?别急,再看buff/cache那列:12G。说明那12G是内核拿去做缓存了。再看最后一列available,12G。这才是系统真正能分配给新程序的可用内存量。

available的计算逻辑大致是:free列的剩余内存 +buff/cache里可以被回收的部分 - 内核自己预留的等。也就是说,available已经提前把那部分“能腾出来”的缓存算进去了。所以看内存是否紧张,优先看available,而不是free。

2.2 available才是你该看的那个数

从实际操作层面讲,我建议在日常巡检和监控报警里,把阈值建立在available上。只要available充足,哪怕free是零,系统也能正常分配内存给新进程,不会出现OOM(内存耗尽)或者Swap换页。

这里再介绍一个我个人很习惯的命令组合,比单纯看free -h更细一点:

$ cat /proc/meminfo | grep -E '^(MemTotal|MemFree|MemAvailable|Buffers|Cached|Dirty|Writeback|Shmem|Slab|SReclaimable|SUnreclaim):' MemTotal: 16264340 kB MemFree: 123456 kB MemAvailable: 12789432 kB Buffers: 345678 kB Cached: 10234567 kB Dirty: 1024 kB Writeback: 0 kB Shmem: 2345 kB Slab: 1234567 kB SReclaimable: 1100234 kB SUnreclaim: 134333 kB

/proc/meminfo里的字段能帮你判断缓存的构成。Dirty表示“待写回磁盘的脏页”,Writeback表示“正在写回磁盘”的数据,这两个值如果持续很高,系统可能真的有问题。Shmem是共享内存/tmpfs占用,这个东西在free里被统计在share和buff/cache里,但它并不像普通Cache那样可以干净地回收,后面会专门讲。SReclaimable是可回收的slab内存,比如dentry/inode缓存;SUnreclaim是不可回收的slab,这个值通常不大,如果异常增长也需要关注。

2.3 什么情况下cache高真的出事了

那buff/cache高就一定没问题?当然不是。判断标准要看两个附加条件:一是available是否持续偏低,二是buff/cache本身是不是包含了不可回收的部分。

举一个典型的反例:

$ free -h total used free shared buff/cache available Mem: 15Gi 2.1Gi 800Mi 8.0Gi 12Gi 2.2Gi Swap: 2.0Gi 0B 2.0Gi

注意shared那列,8个G。这说明有大量共享内存或tmpfs文件占着内存,它们挂在buff/cache里,但内核不能像回收普通文件页那样把它们说扔就扔。所以系统明明显示cache有12G,实际可用的available只有2.2G。这种场景才是需要去排查和处理的。这也是我在下一章想展开的重点:哪些“高cache”是需要管的,哪些是不用管的。

3. 真正会导致问题的三种“假cache”

3.1 脏页刷不回磁盘

正常业务里,最典型的“cache高但需要处理”场景是脏页堆积。先讲概念:当进程往文件里写入数据时,数据首先进入page cache,成为“脏页”(dirty page)。脏页会由内核的写回线程(现代内核里是flusher threads)在合适的时候写回磁盘,写成功后就不再是脏页,可以随时释放。

问题出在写回速度跟不上写入速度的时候。假设你有一台机器在做批量数据导入,程序以几百MB/s的速度往磁盘写文件,而磁盘落盘速度只有100MB/s,那么内存里的脏页就会越积越多。在/proc/meminfo里表现为Dirty数值持续上涨,可高达好几个G。此时再从free看,buff/cache高、available低。如果在脏页达到一定比例后内核触发“强制同步写回”,正在执行写的进程会被阻塞,体感就是程序写文件卡顿了一下,严重的时候整机I/O卡死。

这类问题的直接原因往往不是“内存不够”,而是“内存成了写缓冲区的蓄水池,蓄水速度超过泄洪速度”。排查方向是确认磁盘写性能、应用程序的写入模式,而不是简单地清内存缓存。

3.2 tmpfs和shmem撑起来的“伪缓存”

这是一个很容易被忽视的大坑。Linux里有一种特殊文件系统叫 tmpfs,它看起来是一个目录,实际数据全部存在内存里。常见挂载点有/dev/shm、/run、部分发行版的/tmp,还有容器场景里大量使用的内存盘和共享内存段。

当有程序往/dev/shm里写文件,或者用POSIX共享内存、System V共享内存分配内存时,对应的内存会被计入Shmem,同时也在buff/cache里体现。问题在于,tmpfs文件不会被内核自动回收。只要你没删掉那个文件,它就永远占着内存。很多服务(比如某些Java应用、消息中间件、数据处理任务)喜欢往/dev/shm写临时文件,跑完忘了清理,内存就被“伪cache”给吃了。这也是我在4.2节中用free看共享内存列能发现问题的原因。

处理办法说起来也简单:找到占用共享内存的进程,清掉或停掉,再删掉对应的tmpfs文件,内存立刻回来。难的是定位,命令后面章节会讲。

3.3 被删文件占着位置不放

还有一个比较隐蔽的场景:某个进程打开了一个大文件,在运行期间这个文件被删掉了(比如日志轮转、临时文件自动清理),但进程的句柄还开着。此时这个文件占用的page cache不会被内核释放,因为内核认为还有进程在用这些数据。结果就是磁盘上文件没了,内存里的缓存却还在,buff/cache居高不下,而且不好释放。

这种场景排查时有一个经典命令:

lsof +L1 | grep deleted | sort -k7 -rn | head -20

它的作用是列出所有“已删除但仍被占用”的文件,按大小倒序排。看到那些几百M甚至几个G的deleted文件,基本可以锁定罪魁祸首了。找到进程后,要么重启该进程释放句柄,要么协调业务方把临时文件的管理策略改掉。注意这个命令需要一定权限,很多系统文件只能看到自己的,排障时要确保是root用户,容器里看不到宿主机其他进程,需要到宿主机层面执行。

4. 手动清理的正确姿势

4.1 drop_caches到底该怎么用

先明确一点:正常运维不建议没事就去清缓存。page cache对文件读取性能非常重要,你把它清了,下次读文件又要重新走磁盘I/O,系统整体性能反而会下降。但有些场景确实需要手动释放,比如刚跑完一个超级耗内存的批量任务,内存里攒了大量一次性缓存,而紧接着要启动一个大内存服务,这个时候临时清一次是合理的。

手动清理的命令是往/proc/sys/vm/drop_caches写值:

# 先同步,把脏页刷回磁盘 sync # 清理页缓存(page cache) echo 1 > /proc/sys/vm/drop_caches # 清理slab中的可回收部分(主要是dentry/inode缓存) echo 2 > /proc/sys/vm/drop_caches # 清理以上两者 echo 3 > /proc/sys/vm/drop_caches

写入1、2、3分别对应不同清理范围。写入之后这个文件的值一般又会自动变为0,这是正常现象,表示清理过程已经触发完毕。需要特别强调的是:drop_caches不会释放共享内存/tmpfs的占用,也不会把不可回收的slab清掉。所以如果前面说的“假cache”场景占了主要内存,调用这个命令基本没用。

在生产环境执行前,一定先确认系统没有大量脏页,否则建议先sync。虽然drop_caches不会直接丢数据,但干净地落盘总是更稳妥。历史上我见过有同事不执行sync直接echo 3 >,当时没什么问题,但原理上这等于在内存里还有未落盘数据时强行清理缓存,风险没必要冒。

4.2 通过内核参数让写回更积极

如果是脏页堆积导致的问题,清缓存只能治标,治本要看内核参数。核心是/proc/sys/vm/dirty_background_ratio和/proc/sys/vm/dirty_ratio。

  • dirty_background_ratio:当脏页占内存的比例超过这个阈值,内核的后台写回线程开始把脏页刷到磁盘。默认常见值是10%,比如16G内存,大概1.6G脏页时开始后台刷。这个过程的特征是异步的,不太影响应用写文件。
  • dirty_ratio:当脏页占比超过这个阈值,用户进程自己的写入操作会被阻塞,必须同步参与写回。默认值在不同内核里有差异,常见是20%~30%。这个过程是同步的,写文件的应用能明显体感“卡一下”。

所以当你看监控发现Dirty长期维持在很高水平、业务写文件时出现周期性卡顿,正确思路是调低dirty_background_ratio,让内核更早、更频繁地在后台把数据落盘,尽量避免冲到dirty_ratio触发同步写回。临时调法:

sysctl -w vm.dirty_background_ratio=5 sysctl -w vm.dirty_ratio=15

想永久生效,写到/etc/sysctl.d/99-dirty.conf:

vm.dirty_background_ratio = 5 vm.dirty_ratio = 15

然后执行sysctl --system加载。这里有个注意点:内存特别大的机器(比如几百G内存),百分比会放大成一个很大的绝对值,容易造成脏页积累过多。针对大内存场景,更推荐用字节上限来控制,比如设vm.dirty_background_bytes=268435456(256MB),这样阈值不会因为总内存大而上浮。注意dirty_background_bytes和dirty_background_ratio是二选一的关系,设置了 bytes 后 ratio 会自动失效,二者不要同时设置。

调整的时候要克制,别把dirty_background_ratio调得太低,比如调到1%,那样内核会频繁刷盘,磁盘I/O压力反而增大,写密集业务吞吐会掉。我建议从5%起步观察。

4.3 从应用层把缓存问题“消灭在源头”

内核参数只能调整回收行为,真正稳妥的解法是在应用层控制内存的“制造量”。举几个我在实践中见过的高频例子:

  • 有人写日志或数据导出脚本,用单个进程无限往磁盘写大文件,写几天内存就吃紧。解决方案是尽量走标准库/系统日志轮转机制,限制单文件大小,让老数据及时关闭、删除。
  • 有人用Java服务做批量计算,把大量临时文件写到/dev/shm或tmpfs目录,完成任务后没有清理。解决方案是写任务时增加finally清理逻辑,或改用磁盘目录。
  • 数据库这类服务通常有自己管理的buffer pool,比如InnoDB的Buffer Pool就是自己控制内存,不走page cache的自动回收。但数据库的redo log写盘、排序临时文件仍然会用到page cache。这类核心服务所在的机器不要手动清cache,否则很容易让数据库性能体验突然下降。

从架构角度看,如果某个服务的临时文件注定很大,更合理的做法是给它单独挂一块磁盘目录,而不是依赖tmpfs。内存再便宜,也不是拿来装临时文件的。

5. 一个完整排查案例:文件服务器可用内存告急

5.1 现场症状和第一轮排查

我处理过一台文件下载服务,现象是监控面板上buff/cache占了将近12G,MemAvailable只剩几百M,页面访问偶发卡顿。第一反应当然是先看free -h:

$ free -h total used free shared buff/cache available Mem: 15Gi 2.1Gi 800Mi 8.0Gi 12Gi 2.2Gi Swap: 2.0Gi 0B 2.0Gi

注意shared那列直接8个G,这个信号非常明显,可以初步怀疑共享内存或者tmpfs占了大量空间。继续看/proc/meminfo的关键字段:

Shmem: 8388608 kB Dirty: 0 kB SReclaimable: 123456 kB SUnreclaim: 23456 kB

Dirty几乎为零,说明不是脏页堆积;Shmem8G,问题大概率就出在这了。再用df -h | grep tmpfs看一眼挂载情况:

$ df -h | grep tmpfs tmpfs 7.8G 8.0G 0G /dev/shm

/dev/shm 基本被塞满了,这说明肯定有进程往里面写过大量数据,而且没清理。

5.2 定位到真正的元凶

知道共享内存盘被塞满,下一步就是找谁在写。两个命令搭配着用。先看/dev/shm下的文件:

$ ls -lahR /dev/shm/ total 8.0G drwxr-xr-x 2 root root 4096 ... -rw------- 1 java java 8.0G ... some-java-service.tmp

很好,一个Java服务往/dev/shm写了一个8G的临时文件,而且还没删。接着确认到底是哪个进程:

$ lsof /dev/shm/some-java-service.tmp COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME java 12345 java mem REG 0,21 8589934592 123 /dev/shm/some-java-service.tmp

到这里基本就水落石出了。这个Java服务在运行期间申请了8G的共享内存,用完之后没有主动清理,加上进程还在运行,所以/dev/shm里的文件一直占着内存。由于tmpfs不在内核自动回收的范围内,显示出来的就是“cache高、available低”的假象。

5.3 落地解法与效果

处理方案分成两步。第一步是止血:停掉异常服务,删掉临时文件,内存立刻就能释放不少。如果业务要求服务不能停,可以协调业务方在下次重启时优化资源申请逻辑,或者在运行时增加临时文件清理操作。第二步是长效措施:如果该业务确实需要大块共享内存,建议把tmpfs挂载点改大或者改用磁盘目录,防止它把系统内存空间吃死。我当时的处理是先让服务方清理了临时文件,然后再监控内存指标,available很快就恢复到了10G以上,页面卡顿也随之消失。

这个案例之所以典型,是因为它完美解释了“为什么缓存高不见得是缓存本身的问题”。单纯看buff/cache高,不去区分Shmem和普通page cache,很容易把解决问题的方向带偏。

6. 常见误区、高频问题与避坑速查

6.1 关于cache清理的几个错误认知

在社区里看到很多人讨论“Linux内存占用过高”,经常能看到一些被当成万灵丹的办法,实际并不可取。

第一个误区是“把 cache 清了就万事大吉”。实际上,如果系统里有大量的dirty页,或者Shmem占大头,drop_caches根本没有效果。即便能清掉,过几分钟cache又会涨回来,因为业务还在读写文件,内核天然会重新建立缓存。你只是把一个“正在被利用的资源”变成了“空闲资源”,系统整体没赚到任何东西。

第二个误区是“cache占用高就该加内存”。这个结论要分场景。如果available长期很低、而且定位到是业务缓存(tmpfs、共享内存)不可回收导致的,加内存可以缓解;但如果只是普通page cache高,说明文件读取频率高,加内存反而会让cache变得更大,不一定解决根本问题。该加速的是I/O瓶颈,不是内存总量。

第三个误区是“所有缓存都是洪水猛兽”。对于文件服务器、数据库、Web服务来说,page cache恰恰是性能保障。把它清零意味着热数据的读取全都要重新走磁盘,业务响应时间会明显恶化。我之前在一台nginx文件下载机器上做过测试,清完cache后首次下载延迟上涨了几倍,后续热度恢复后才慢慢回落。所以在没有明确问题前,真的别手痒。

6.2 别把系统锁文件报错误认为内存缓存问题

在搜索“Linux cache 占用高”时,经常会碰到另一个完全不相关的问题:waiting for cache lock: could not get lock /var/lib/dpkg/lock-frontend。这是Debian/Ubuntu系统上apt或dpkg的锁文件冲突,含义是“有一个软件包管理操作正在运行,其他操作必须排队等待”,跟内存缓存没有半毛钱关系。

如果你是在Ubuntu上执行安装命令时看到这个报错,正确排查思路是看后台是不是有apt、unattended-upgrades之类的进程在跑。等它跑完,或者必要时确认没有安装任务后清理锁文件,而不是去研究内存cache。这个点放在这里提醒一下,是因为实在太容易搜混了:两个东西都叫“cache”,一个是内存管理概念,一个是文件锁,千万别张冠李戴。

6.3 速查表:什么情况该动、什么情况不该动

下面这个表是根据我日常经验整理的判断参考,可以直接收藏备用:

症状判断结果该做什么
buff/cache高,available充足健康状态,内核拿空闲内存做Cache不用管,别清缓存
buff/cache高,available低,Dirty持续高脏页堆积,写回跟不上调低dirty_background_ratio,排查磁盘写性能,必要时适量调低dirty_ratio
buff/cache高,available低,Shmem高tmpfs/共享内存塞满了定位 /dev/shm、/run 等tmpfs挂载点,清理临时文件或调整应用逻辑
buff/cache高,available低,发现有大量deleted文件文件被删但句柄未释放lsof +L1定位进程,重启进程或协调业务方释放句柄
刚跑完一次大任务,内存里都是冷Cache临时高cache,业务即将需要大内存先sync,再echo 3 > /proc/sys/vm/drop_caches,不建议常驻定时执行

再补充一个排查命令集合,一次性把关键信息看全:

free -h cat /proc/meminfo | grep -E '^(MemTotal|MemAvailable|Dirty|Writeback|Shmem|Slab|SReclaimable|SUnreclaim)' df -h | grep tmpfs lsof +L1 | grep deleted | sort -k7 -rn | head -20

这几条命令基本能覆盖绝大多数“cache高”的定位需求。

最后再分享一个我的个人感受。在Linux上,“内存占用高”是一个极其容易被误解的信号,因为内核的设计目标本来就是把内存物尽其用。排查这类问题,最重要的不是学习怎么一键清缓存,而是学会区分“可回收缓存”和“不可回收内存”。前者高是常态,后者高才是故障。搞清楚这个区别,你就能在告警面前保持镇定,也能在真正出问题的时候一击即中。反正我现在的习惯是:先在/proc/meminfo里把Dirty、Shmem、SReclaimable这几个字段扫一眼,再决定下一步往哪走。这个顺序帮我避开过很多次无效操作,也算是在反复踩坑之后沉淀下来的一个笨办法,希望能帮你少走点弯路。

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

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

立即咨询