服务器跑得好好的一登录执行 free -h 看到 buff/cache 占了十几个 Gused 也飙到百分之八九十CPU 倒是很闲业务进程用 top 看也就吃了几 G这到底是不是内存泄漏该不该重启先说结论大部分情况下不用慌这个现象在 Linux 下非常正常属于内核内存管理机制在正常工作——但也存在少数场景真的要干预。这篇就基于我多年的踩坑经验把 buffer/cache 高占用的原理、判断方法和处理手段一次性讲透让你拿到一台负载不明的机器时能快速判断“该干嘛”而不是凭感觉瞎调。1. 先搞懂 free 输出里的内存账本1.1 一行 free -h 到底在讲什么很多人上来就是 free -h 看一眼然后盯着 used 的数值焦虑。其实 Linux 的内存统计口径远比表面复杂。执行一下 free -h你会看到类似这样的输出$ free -h total used free shared buff/cache available Mem: 31Gi 6.2Gi 12Gi 1.2Gi 13Gi 23Gi Swap: 8.0Gi 0B 8.0Gi这里几个字段的定义是total物理内存总量。used系统当前实际正在使用的内存它包含了进程占用的内存、内核占用的内存但不包含 buff/cache。free完全空闲、尚未被分配的内存。shared主要用于 tmpfs 等共享内存多个进程可以共享访问。buff/cache内核用来做块设备缓冲区buffer和页面缓存page cache的内存总量。available这是一个估算值表示在不触发内存交换、不显著影响系统性能的前提下还能分配给新进程的内存。对用户来说这个值比 free 更有参考意义。注意上面的示例里used 只有 6.2G但用户往往盯着 “buff/cache 13Gi” 感觉内存被吃掉了。实际上这 13G 并没有被“占用”而是被内核当作加速缓存。当你的应用需要更多内存时内核会在很短时间内把缓存回收并分配出去。提示available 是老内核3.x 之前没有的字段从 util-linux 2.20 版本开始免费提供。如果某些精简系统的 free 没有 available可以直接看 /proc/meminfo 里的 MemAvailable更准确。1.2 buffer 和 cache 到底有什么区别这俩其实在 Linux 内核里合并统计但本质属于不同的缓存层次。buffer指块设备比如硬盘、SSD的缓冲区主要缓存文件系统的元数据inode、目录项等以及块设备的原始读写请求。它的作用是为尚未写入磁盘的数据提供临时缓冲减少物理磁盘 I/O 次数。cache这里通常说 page cache是按页缓存的文件内容。当你读取文件时内核会把文件内容放入 page cache下次再读同样的文件就直接从内存返回避免 I/O。写文件时内核也会先把数据放进 page cache积累一批后再落盘。简单类比buffer 更像是“给水龙头接了个桶”把零散的水流攒成大桶再一次性倒掉cache 像是“书架”看过的书先放案头再要查就不用去仓库翻。内核在计算内存占用时这两类内存都可以被回收复用所以从用户应用的角度来说它们不是“不可释放的占用”而是“可回收的缓存”。2. buffer/cache 为什么会“居高不下”2.1 Linux 内核的“内存不用就是浪费”策略Linux 的设计哲学里有一条闲着也是闲着不如把空闲内存用来做缓存。只要内存没有到紧缺的程度内核就会尽量扩大 page cache 的使用范围。当你的程序读取了一片 GB 级的大文件后这一部分数据会留在 cache 里free 会一直显示可用内存很小。这其实正是内核在努力提升性能的表现。那么什么时候会出现内存真正不够用答案是当可用内存不足、需要分配新内存时内核必须启动回收流程。回收的顺序一般是先回收可回收的页page cache、buffer实在不行再换出匿名页swap。所以buff/cache 高并不等于内存压力高。2.2 脏页回写机制cache 不是一直赖着不走虽然 page cache 是“可回收”的但有一种情况比较特殊——脏页dirty pages也就是被修改过但还没有写回磁盘的数据页。这类页不能直接回收必须先写回磁盘而这个写回操作是有代价的受几个内核参数控制参数默认值作用vm.dirty_ratio20当脏页占总内存的百分比达到该值时触发强制同步写回vm.dirty_background_ratio10当脏页占总内存的百分比达到该值时内核会在后台慢慢写回vm.dirty_writeback_centisecs500后台写回进程pdflush/flusher多久唤醒一次单位百分之一秒vm.dirty_expire_centisecs3000脏页在内存中最多存活多久超过后必须被写回单位百分之一秒我用实际遇到过的情况举例有一台机器在做数据库冷备份直接把一个大文件从磁盘读出来再 tar 到另一个目录结果 buff/cache 持续暴涨到内存总量的 95%而且没有明显下降。原因就是备份进程持续制造脏页flusher 线程来不及落盘。后来我发现 dirty_background_ratio 默认是 10%对 256G 内存的机器来说这意味着后台允许攒到 25G 的脏页再开始刷盘积累量相当可观。2.3 为什么缓存很高不等于内存泄漏内存泄漏是指进程不再使用的内存没有被释放导致可用内存持续下降并且无法通过回收缓存来恢复。而 buff/cache 高是内核主动使用的“缓存”进程结束或内存压力变大时内核会把它让出来。判断是不是泄漏有一个很实用的方法——隔一段时间看同一个进程的 RES 值是否持续上涨以及 top 里有没有某个进程的 %MEM 异常增长。如果 top 里所有进程加起来的内存占用远小于 free 里的 used那 used 里的一部分其实是内核自己用的比如 slab 不可回收部分和内核模块这部分是正常的。可用内存看 available 而不是 free。3. 怎么判断“是不是真的有问题”3.1 别被 used 百分比骗了很多监控系统直接拿 total - free 当作“内存使用率”这个口径在 Linux 下会严重高估。假设你申请了一台 32G 的云主机跑了 Nginx 和 Java 应用Java 堆设置了 4GNginx 加各种进程占 2G理论上 used 也就 6G 左右但 free 的 used 字段可能因为文件读取变得很大。监控告警显示内存 95%SSH 上去一看进程列表里根本没有吃 20G 内存的进程这是典型的 page cache 被算进了 used。正确的判断方法是看 available 而不是 free。用 top 的 “%MEM” 和 ps aux 的 RSS 累加对比 available 数值。观察系统是否有大量 swap in/out以及 CPU 的 iowait 是否过高。注意部分老版本监控组件如早期篇幅的某些云监控脚本会用free -m直接算 used/total对 buff/cache 处理不当产生误告警。如果你用的监控面板长期报内存高建议检查它读取的是 MemAvailable 还是仅仅 free。3.2 这几类指标才真正反映内存压力真正值得关注的是下面这几个swappinesscat /proc/sys/vm/swappiness默认通常是 60。该值表示内核在回收内存时使用 swap 的积极程度。数值越大越倾向交换匿名页越小越倾向回收 cache。如果服务器内存足够且缓存收益高把它调小往往更好。pswpin / pswpoutvmstat 1里的 si 和 so 字段如果持续大于 0说明系统正在发生大量换入换出内存压力真实存在。直接回收效率查看/proc/vmstat里的pgscan_direct、pgsteal_direct等字段数值飙升时说明内存回收不够及时已经在穿越路径上产生性能损耗。内存带宽与 I/O 等待iowait 过高同时伴随 buff/cache 上升很可能与大量写缓存刷盘有关此时数据落盘成为瓶颈不只是内存问题。很多运维一看到 swap 使用率变高就紧张但如果系统内存完全被 cache 占满长期没有换页swap 只是备用那么 swap 高并不代表性能差。反过来如果 swap 在持续读写那才是真内存不足。4. 核心解决办法与操作要点4.1 临时手动释放缓存什么时候用、怎么用早期网上流传的做法是sync echo 1 /proc/sys/vm/drop_caches echo 2 /proc/sys/vm/drop_caches echo 3 /proc/sys/vm/drop_caches参数含义1释放 page cache文件内容缓存。2释放 dentry 和 inode目录项和索引节点缓存。3同时释放 page cache、dentry 和 inode。这个操作不是不可以做但要注意场景。释放缓存其实是把“已经占用的磁盘读加速”清掉下次读文件还得重新从磁盘读反而可能降低性能。所以我个人只在两种情况下会手动 drop_caches刚完成批量复制或解压等一次性大操作缓存对业务无意义且希望在监控面板上反映真实内存余量。准备做内存压力测试需要干净的基线状态。在生产环境、高峰期一般不建议用 drop_caches尤其是数据库服务器。数据库有数据文件page cache 本来是加速读的你把它清了SQL 查询反而可能变慢。如果确实要执行请先sync让脏页落盘避免数据丢失风险。这个操作需要 root 权限或者至少要具备对该文件的写权限。部分容器环境或加固系统禁止修改需要先确认。4.2 调整内核参数让缓存更“节制”最简单的持久化调优是修改/etc/sysctl.conf例如vm.vfs_cache_pressure 50 vm.swappiness 10 vm.dirty_background_ratio 5 vm.dirty_ratio 15vm.vfs_cache_pressure控制内核回收 dentry/inode 缓存的倾向性默认 100。调得越低内核越倾向于保留目录项缓存调得越高缓存越容易被回收。对于访问文件频繁的机器可以调到 50~80如果有大量小文件操作且内存紧张可以调高到 200 甚至更高。vm.swappiness很多人直接从 60 改成 0其实没必要。改成 10 左右已经能明显减少 swap 使用同时保留一定的回收匿名页能力。完全设置为 0 在某些内核版本可能导致 OOM 风险增加因为内核在内存充足时不愿意换出匿名页只能靠回收 cache 甚至直接 kill 进程。dirty_background_ratio / dirty_ratio这两项直接影响缓存写回节奏。如果发现脏页积压导致 I/O 尖峰可以把 background 调低让后台刷盘更悠闲避免集中写盘。但如果业务是大量顺序写调太低反而频繁刷盘影响吞吐。修改后执行sysctl -p生效。注意数值不是越小越好需要结合业务特点反复测试。我之前遇到过一台文件服务器为了“释放内存”把 vfs_cache_pressure 调成 200结果大量目录访问变慢因为 dentry 缓存频繁被清每次 stat 都要重新走磁盘教训相当深刻。4.3 从根上优化应用程序内存意识要跟上内核参数只是“心情调节器”真正健康的方式是让应用层合理利用内存。以下是一些常见手段给 Java 等有堆内内存的应用设置适当堆大小不要默认拿整机内存的一半。Nginx、MySQL、Redis 各自规划好内存 buffer避免多个服务争抢。如果有批量数据处理任务可以分片执行减少瞬时 page cache 压力。使用 cgroup 限制单容器/进程的内存上限防止某个业务疯狂占用内存拖垮整机。文件读完后如果明确不再需要可以调用posix_fadvise并传POSIX_FADV_DONTNEED提示内核释放对应缓存。这个属于高级玩法用到的情况不多但在写数据管道时能有效控制缓存膨胀。说实话大部分“内存占用过高”的投诉最后排查下来要么是监控口径不对要么是单个业务进程配置不合理。调整内核参数的收益往往不如把乱配置业务的 JVM 堆改小来得立竿见影。4.4 特殊情况能不能直接禁用 cache有人会问既然 cache 让 free 显示太少能不能在内核启动参数里直接关掉 page cache答案是基本别想也不建议想。Linux 内核的 page cache 是文件读写的核心加速机制把它关掉意味着所有读写都直接穿透到磁盘性能会断崖式下跌。内核没有提供“完全禁用 page cache”的开关也不可能有健康的生产系统这样做。另外补充一点很多时候内存占用看着吓人是因为你没有意识到还有slab内存和不可回收内核内存。/proc/meminfo里的 SReclaimable 和 SUnreclaim 合计构成 slab其中不可回收部分比如内核某些数据结构才是真正拿不出来的。如果发现 SUnreclaim 异常大可能是内核模块、驱动或文件系统有异常线程多的应用也会造成内核内存增加。这跟 page cache 是两码事别混着调。5. 常见问题与排查技巧实录5.1 为什么 free 显示 used 高但 top 里看不到大进程这是最经典的问题。原因通常是大量 page cache 被算进 used但 top 里进程 RSS 总和并不包括 page cache。有内核线程或 slab 占用了内存但 top 默认不显示内核线程的内存细节。tmpfs 或共享内存如 /dev/shm、Docker 容器日志文件占用空间free 会统计到 shared累计大后也影响 used。排查命令# 查看内存整体与缓存细分 cat /proc/meminfo # 按内存占用排序显示进程 top -o %MEM # 查看 slab 使用情况 cat /proc/slabinfo | awk {print $1, $2, $3} | sort -k3 -n -r | head -20我遇到过一个极端案例某台机器用了 Docker容器把日志写到 json-file而 json-file 底层是文件系统所有日志内容都会进入 page cache虽然容器进程本身只占几百 M宿主机内存却被日志缓存完全填满。解决方式是限制容器日志大小或把日志直接写到磁盘并加日志轮转缓存才会降下来。5.2 为什么 drop_caches 之后缓存很快又满了因为你的业务还在访问文件内核会立刻再次把新读到的页面填进 page cache。这不是 bug说明系统很忙缓存有实际价值。如果缓存被清掉后并不再增长说明当前负载下没有大量文件 I/O那之前的缓存高可能来自历史操作比如刚解压了一个大包。真正应该做的不是“清一次”而是搞清楚是什么进程在持续产生文件 I/O# 使用 iotop 查看哪些进程在读写 iotop -o # 使用 pidstat 看进程的读写速率 pidstat -d 1还有一个经典排查工具是bcc或bpftrace可以跟踪 page cache 的添加来源不过对新手略重非必要不需要上。5.3 容器环境的缓存问题要特别注意有一种特殊情况是容器内查看内存。容器内执行 free看到的是宿主机的内存统计而不是容器本身的配额。正确查看容器内存使用应该用cat /sys/fs/cgroup/memory.current cat /sys/fs/cgroup/memory.max在 cgroup v2 环境下这两个文件分别表示当前使用量和上限。很多容器基础镜像里没有 procps 工具free 不一定可用不如直接读 cgroup 文件准确且不依赖额外包。踩过的坑曾经有个容器进程明明在 cgroup 限制内却因为宿主机 page cache 巨大而触发 OOM这是因为容器内存统计的 page cache 同样计入 cgroup 限制。解决办法是把容器的页缓存回收压力调一下或者在容器应用侧尽量用 O_DIRECT 绕过 page cache避免缓存全部计入容器配额。5.4 OOM 与缓存的关系什么时候真的会出事虽然 page cache 可回收但回收并不是瞬间完成的特别是内存压力来得又快又猛时内核还没来得及把脏页写回磁盘新的内存请求又来了就可能触发 OOM killer。典型的场景是突然启动一个大内存应用同时还有大量脏页需要落盘内存瞬间不够内核只能杀进程缓解压力。预防措施给关键服务配置合适的oom_score_adj防止 OOM 时误杀重要进程。把 dirty 参数调低避免脏页长期积压。提前 plan 好内存余量不要把所有内存都占满。使用 systemd 或容器管理工具的MemoryLimit/MemoryMax控制资源使用。注意OOM 时查看/var/log/messages或dmesg里的 “Out of memory: Kill process” 记录可以看到当时的内存分布对后续调参非常有帮助。6. 我常用的调优参考组合下面分享一套我多年实践中比较通用的参数组合适用于常规的文件服务器、Web 服务器和中等规模的 Java 应用服务器。不是唯一答案但可以做初始参考# /etc/sysctl.d/99-memory-tuning.conf # 减少 swap 倾向优先回收缓存 vm.swappiness 10 # 让内核尽量保留目录项缓存适合文件访问多的场景 vm.vfs_cache_pressure 50 # 控制脏页写回避免集中刷盘造成 I/O 抖动 vm.dirty_background_ratio 5 vm.dirty_ratio 15 # 若机器内存极大比如 64G 以上可以改用字节数控制更灵活 # vm.dirty_background_bytes 1073741824 # vm.dirty_bytes 2147483648 # 确保可用内存监控口径正常 vm.zone_reclaim_mode 0注意一个细节dirty_ratio和dirty_bytes不能同时设置内核会以后设置的为准建议统一用百分比或统一用字节数。对大内存机器百分比设置可能产生一个非常大的阈值比如 256G 内存的 20% 是 51G等到刷盘时可能卡顿严重所以建议大内存机器使用字节数设置。改完参数后别急着下结论最好跑一轮压测或观察负载正常业务一段时间。tuning 这件事没有长期数据支撑不要轻易拍板参数看着合理不代表线上一定最优。这套组合我已经在多台机器上稳定运行最直观的体验是free 的 used 依然包含大量 cache但 available 始终充足监控告警不再乱报真遇到内存吃紧时也有足够的回收余量。实际测试时用vmstat 1观察 si/so 明显减少大文件 I/O 时 iowait 尖峰也平滑了很多。当年我刚开始接触 Linux 运维时也经历过半夜被内存告警短信叫醒、上去一顿 drop_caches 操作的经历后来才明白这其实是内核的正常工作状态。现在就一个原则多关注 available、合理调整参数、重视应用层的内存规划而不是跟 page cache 较劲。希望这篇能帮你在下一次遇到 buff/cache 过高时少走点弯路。