
1. 从一次内存去哪了的困惑说起刚接手一台线上服务器那会儿我第一件事就是敲free -h看内存余量。结果屏幕上赫然写着总内存 16G已用 12G可用只剩 3G 出头。当时心里咯噔一下赶紧翻进程列表ps aux按内存排序把所有进程的 RSS 加起来也就 5G 左右。那剩下的 7G 到底被谁吃了是不是有内存泄漏是不是被什么看不见的东西占着后来把free的输出看仔细了才发现buff/cache那一栏写着 7G 多。再后来跑了一段时间发现这个数字还在涨但系统一点没变慢反而磁盘读写的响应越来越快。这时候我才真正意识到buff/cache不是被吃掉的内存而是内核主动拿来干活的一块临时工作台。它既不是泄漏也不是浪费而是 Linux 内存管理里最容易被误解、也最值得搞清楚的一块。这篇内容就围绕Linux 内存中的 Buffer 与 Cache展开。我会把这两者到底是什么、内核怎么管理它们、为什么free里的 available 和 free 差那么多、什么时候该担心什么时候不用管、以及怎么观测和调优一条线讲透。适合刚接触 Linux 运维的读者也适合那些被buff/cache数字吓过、想彻底弄明白背后机制的人。文中涉及的命令和参数都可以直接拿去复现我会把每一步为什么这么看讲清楚而不是只丢一堆命令。先给一个最朴素的结论打底Buffer 和 Cache 都是内核用空闲内存做的缓存目的是减少对慢速块设备的直接访问。区别在于 Buffer 更偏向块设备层面的原始数据块Cache 更偏向文件系统层面的文件页。这个区分在现代内核里其实已经模糊了但理解它们的来龙去脉对读懂内存指标至关重要。2. Buffer 与 Cache 到底各管什么2.1 用生活场景理解两者的分工要理解 Buffer 和 Cache最直观的类比是仓库管理员和图书管理员的区别。Buffer 像是仓库门口的装卸缓冲区。货车块设备拉来一车货不会直接一件件往货架上摆而是先堆在门口的缓冲平台上等凑够一批再统一入库。这个平台就是 Buffer它面对的是块这个粒度——磁盘读写的最小单位是扇区通常 512 字节或 4K内核不会为了一个字节就去碰磁盘而是先把整块数据读进 Buffer需要的时候再从里面取。Cache 则像是图书管理员脑子里的记忆。你借过一本书管理员记住了内容下次你再问同一本书他直接凭记忆回答不用再跑一趟书库。这里的书就是文件页Cache 缓存的是文件内容粒度是页通常 4K。当你第二次读同一个文件内核发现这个页已经在 Cache 里直接返回完全不碰磁盘。两者的共同点是用内存换时间区别在于缓存的对象层级不同Buffer 缓存的是块设备上的原始块Cache 缓存的是文件系统里的文件页。在早期的 Linux 内核2.4 之前这两者是分开统计、分开管理的两套缓存。到了 2.4 之后内核把页缓存page cache和缓冲区缓存buffer cache做了统一文件页的元数据和块数据共享同一套页管理机制所以你在free里看到的buff/cache是合并后的数字。2.2 为什么现代内核把它们合并统计这里有个很多人忽略的历史细节。早期内核里读一个文件会经历磁盘块 → buffer cache → page cache → 用户空间这样一条链路同一份数据可能在两层缓存里各存一份内存利用率很低。2.4 内核引入了统一页缓存的设计文件的数据页直接由 page cache 管理而 buffer head 只作为页内块映射的辅助结构存在不再单独持有一份数据副本。所以今天你在free里看到的buff/cache绝大部分其实是 page cacheBuffer 占的比例通常很小。这也解释了一个常见现象跑数据库或者大量小文件读写的机器buff/cache涨得特别快因为文件页缓存被大量填充。而纯计算型、几乎不碰磁盘的机器这个数字就很稳定。理解这一点很关键因为它决定了你排查问题的方向。如果buff/cache很大但系统流畅基本不用管如果它很大同时系统卡顿那要查的是缓存回收是否跟不上分配速度而不是缓存本身有问题。2.3 一个容易混淆的点Cache 不等于程序缓存新手常把free里的 cache 和应用程序自己的缓存搞混。比如某服务在内存里维护了一个热点数据字典占了几百兆这部分算在进程的 RSS 里跟内核的 page cache 完全是两码事。内核的 cache 是文件内容的副本应用程序的缓存是业务数据的副本前者由内核自动管理、可随时回收后者由程序自己管理、内核动不了。这个区分在排查内存问题时特别重要。曾经有个场景某服务 RSS 持续上涨同事第一反应是是不是 page cache 算进去了其实不是page cache 不计入进程 RSS。RSS 涨就是进程自己真的在吃内存得从程序逻辑或者内存分配器上找原因。把这两者分清楚能少走很多弯路。3. 内核怎么决定留多少、扔多少3.1 空闲内存不是闲着而是待命很多人对内存有个直觉空闲内存越多越好。但在 Linux 的设计哲学里空闲内存是一种浪费。物理内存的访问速度是纳秒级磁盘是毫秒级差了好几个数量级。既然内存闲着也是闲着不如拿来缓存磁盘数据让后续访问变快。这就是 page cache 存在的根本动机。所以内核的策略是只要有空闲内存就尽量用来做缓存当应用程序需要内存时缓存可以随时被回收让位。这个随时让位的机制就是free里available那一栏的含义——它估算的是在不触发 swap 的前提下还能拿出多少内存给新程序用其中就包括了大部分可回收的 page cache。换句话说free小不代表内存紧张available才是真正该看的指标。一台跑了很久的机器free接近 0 但available还有好几个 G是完全健康的状态。我见过不少人看到free只剩几百兆就慌着去重启服务其实纯属多虑。3.2 页回收的两条路径直接回收与后台回收当应用程序申请内存、而空闲页不够时内核会触发页回收。回收分两条路径直接回收direct reclaim申请内存的进程自己停下来同步去回收页。这条路径会让申请者阻塞表现为业务延迟抖动。后台回收kswapd内核的后台线程异步回收提前把脏页写回、把干净页释放维持一定的空闲水位。内核为每个内存管理区zone设了三条水位线min、low、high。空闲页高于high时kswapd 睡觉降到low以下kswapd 被唤醒开始回收降到min以下所有分配都得走直接回收甚至触发 OOM。这套水位机制是理解内存压力的核心/proc/zoneinfo里能看到每个 zone 的实时水位。实际排查时如果发现业务偶发卡顿可以去看/proc/vmstat里的pgscan_direct和pgsteal_direct计数。这两个数字持续增长说明系统在频繁走直接回收内存确实吃紧了该考虑扩容或者优化缓存占用。3.3 脏页回写缓存里最贵的那部分page cache 里的页分两种干净页和脏页。干净页和磁盘内容一致回收时直接丢弃即可成本极低。脏页是被修改过、还没写回磁盘的页回收前必须先写回否则数据就丢了。写回是磁盘 I/O慢所以脏页是缓存里最贵的部分。内核通过几个参数控制脏页的回写节奏这些参数在/proc/sys/vm/下参数含义常见默认值dirty_ratio脏页占总内存的百分比上限超过则进程写操作被阻塞强制回写20dirty_background_ratio脏页占比达到此值时后台线程开始异步回写10dirty_expire_centisecs脏页在内存中停留多久后被标记为可回写单位 1/100 秒3000dirty_writeback_centisecs后台回写线程的唤醒间隔500这几个参数的取舍很有意思。dirty_ratio设太大脏页堆积过多一旦触发强制回写会造成 I/O 尖峰业务写延迟飙升设太小回写太频繁磁盘一直在忙吞吐上不去。默认的 20/10 对大多数场景够用但如果是写密集型业务比如日志服务、消息队列适当调低dirty_background_ratio让回写更平滑往往比调大更有效。提示调这些参数前先用iostat -x 1观察一段时间的磁盘 util 和 await确认瓶颈确实在回写而不是盲目改数字。改完用vmstat 1看bo块写出是否变得平稳。4. 把内存指标看穿的实操方法4.1 free 输出逐字段拆解先看一个典型的free -h输出total used free shared buff/cache available Mem: 15Gi 5.2Gi 412Mi 328Mi 10Gi 9.6Gi Swap: 2.0Gi 0.0Ki 2.0Gi逐栏解释total物理内存总量扣掉了内核保留的部分。used已用内存等于 total 减去 free 减去 buff/cache。注意它包含了共享内存和内核态占用。free完全没被使用的内存这个数字小很正常。sharedtmpfs 等共享内存占用这部分算在 used 里但可被多进程共享。buff/cache内核缓存总和大部分可回收。available估算的可用内存包含 free 加上可回收的缓存是判断内存是否紧张的关键指标。判断内存是否健康我的习惯是看available / total的比例。高于 20% 基本无忧低于 10% 要警惕低于 5% 且pgscan_direct在涨就该动手了。单看free那一栏没有任何意义。4.2 用 /proc/meminfo 定位缓存构成free是个汇总视图想看细节得读/proc/meminfo。几个关键字段Buffers块设备缓冲通常很小。Cachedpage cache 主体包含文件页和 tmpfs。SReclaimable可回收的 slab比如 dentry、inode 缓存这部分也算进 buff/cache。Dirty脏页大小反映待回写的数据量。Writeback正在回写中的页。AnonPages匿名页也就是进程堆栈等这部分不可回收只能靠 swap。排查时我常做的一件事是对比Cached和AnonPages。如果Cached远大于AnonPages说明内存主要被文件缓存占了压力不大如果AnonPages持续上涨逼近总量那才是真正的内存吃紧得看是哪个进程在涨。4.3 用 smem 和 slabtop 看隐形占用有时候free显示 used 很高但进程 RSS 加起来对不上差额往往藏在 slab 里。slabtop -s c按缓存大小排序能看到 dentry、inode、各种内核对象占了多少。文件系统上文件数量巨大的机器比如邮件服务器、对象存储节点dentry 缓存能占到好几个 G这部分属于SReclaimable可回收但回收时会带来延迟。smem则能给出更准确的进程内存视图它用 PSS按比例分摊共享页统计比 RSS 更接近真实占用。多进程共享大量库文件的场景下RSS 会严重高估PSS 才靠谱。4.4 一个完整的排查链路示例假设某台机器报警内存使用率超过 90%我通常按这个顺序走free -h看 available确认是真紧张还是缓存撑起来的假象。若 available 低cat /proc/meminfo | grep -E Dirty|Writeback|AnonPages|Cached看构成。vmstat 1 5看si/soswap 换入换出和pgscan相关判断是否在频繁回收。ps aux --sort-rss | head找内存大户。若进程侧对不上slabtop看内核对象。结合业务时间点判断是缓存正常增长还是某进程泄漏。这条链路走下来基本能定位到是缓存正常、进程泄漏还是内核对象膨胀三类问题之一。最怕的是一上来就echo 3 /proc/sys/vm/drop_caches清缓存清完数字好看了问题没解决还把热数据全丢了业务反而变慢。5. 缓存回收与手动干预的边界5.1 drop_caches 到底该不该用/proc/sys/vm/drop_caches是很多人手里的万能按钮写个 3 就把 page cache、dentry、inode 全清了。但我的建议是生产环境慎用测试环境可以用来验证假设。原因很简单清缓存不会释放任何被占用的内存它只是把可回收的缓存提前回收了。清完之后业务再次访问同样的文件又得从磁盘重新读一遍性能下降是必然的。更糟的是如果清缓存时正好有大量脏页会触发集中回写造成 I/O 抖动。真正需要清缓存的场景很少比如做内存基准测试前要排除缓存干扰或者排查某个缓存导致数据不一致的诡异问题时临时验证。日常运维中让内核自己管理就好。5.2 什么时候该调 swappinessvm.swappiness控制内核在回收时偏向回收文件页还是换出匿名页取值 0 到 100。默认 60意思是两者兼顾、略偏向换出匿名页。这个参数的调法争议很大。我的经验是分场景数据库、缓存类服务适当调低比如 10尽量保留匿名页避免热数据被换出后重新加载。桌面或交互式系统保持默认或略高让不活跃的匿名页换出给文件缓存腾地方。容器环境注意 swappiness 是全局参数容器内改了会影响宿主机通常不建议在容器里动。调低 swappiness 不等于禁用 swap。设成 0 也只是尽量不换出在内存真正紧张时内核仍会 swap。想彻底不用 swap 得swapoff但那样一旦内存耗尽就是 OOM风险更大。折中方案是保留一块不大的 swap 作为缓冲配合较低的 swappiness。5.3 cgroup 内存限制下的缓存行为容器化环境里内存限制由 cgroup 控制。这里有个容易踩的坑page cache 计入 cgroup 的内存用量。也就是说一个容器读了大量文件page cache 涨起来可能触发 cgroup 的内存上限导致容器内的进程被 OOM kill哪怕这些缓存是可回收的。内核在 cgroup 层面也做了回收机制内存压力下会优先回收本 cgroup 的 page cache。但在某些内核版本上回收不够及时就会出现缓存把容器撑爆的现象。应对办法有两个一是给容器留足内存余量别把 limit 卡得太死二是对读密集但内存敏感的服务考虑用posix_fadvise的POSIX_FADV_DONTNEED主动提示内核丢弃某些文件的缓存。6. 几个真实场景里的经验与坑6.1 内存泄漏误判缓存增长被当成故障有次某服务上线后监控显示内存使用率一周内从 40% 涨到 85%值班同事准备回滚。我拉了一下/proc/meminfo发现Cached涨了 6 个 GAnonPages几乎没动available还有 3 个 G。结论很清楚服务在大量读文件page cache 正常增长不是泄漏。这类误判非常常见根源是把used 高等同于内存不够。正确的判断依据永远是available和回收行为而不是 used 的绝对值。后来我们把监控面板改成以available为主指标误报少了一大半。6.2 脏页堆积导致的写延迟尖峰另一个场景是写密集型服务偶发延迟飙升。查下来是dirty_ratio用默认值 20机器内存大脏页能堆到 3 个 G 才触发强制回写一回写就是几百毫秒的 I/O 阻塞。把dirty_background_ratio从 10 调到 5让后台回写更早介入延迟尖峰基本消失。这个调整的逻辑是与其让脏页堆到临界点集中爆发不如让回写更早、更均匀地发生。代价是磁盘平均利用率略升但换来了延迟的平稳对在线服务来说这笔账划算。6.3 tmpfs 把内存吃进 used 的陷阱/dev/shm和/run默认是 tmpfs写进去的数据直接占物理内存而且算在shared和used里不属于可回收的 page cache。有次某应用往/dev/shm写临时文件没清理几天后占了 4 个 Gfree显示 used 很高但 buff/cache 不大排查半天才发现是 tmpfs。判断方法很简单df -h /dev/shm看使用量du -sh /dev/shm/*找大户。tmpfs 的坑在于它看起来像文件系统实际是内存写满了要么占内存要么触发 OOM跟磁盘写满完全两回事。6.4 大页与透明大页对缓存统计的干扰开了透明大页THP的机器上/proc/meminfo里的AnonHugePages会占一部分这部分在统计上归入匿名页。有时候看到AnonPages莫名很大一部分可能是 THP 合并造成的。对延迟敏感的服务比如某些数据库THP 的合并和拆分操作会带来抖动通常建议关掉或者设成madvise模式。这个跟 buffer/cache 不直接相关但排查内存问题时经常一起出现值得留意。7. 我个人的几条实操准则折腾这些年关于 buffer 和 cache我沉淀了几条自己的判断准则分享出来供参考。第一看内存永远先看 available不看 free。free 小是常态available 才是真相。这条能挡掉八成的误报。第二缓存涨不是问题回收不动才是问题。判断回收是否健康看/proc/vmstat里的pgscan_direct和pgsteal_direct是否持续增长以及vmstat里的si/so是否有 swap 活动。这两个信号同时出现才是真紧张。第三别轻易 drop_caches。它解决不了任何根本问题只会让热数据变冷。真要用只在测试环境验证假设时用。第四脏页参数按业务调不按默认值躺平。写密集服务把dirty_background_ratio调低一点延迟会平稳很多读密集服务基本不用动。第五容器里对 page cache 要有心理预期。limit 别卡太死给缓存留余量否则容易出现缓存撑爆容器的怪现象。最后再补一个小技巧想快速看某台机器的缓存构成一条命令就够——grep -E ^(MemTotal|MemFree|Buffers|Cached|SReclaimable|AnonPages|Dirty|Writeback|MemAvailable) /proc/meminfo。把这几个字段连起来看比free的信息量大得多排查时能省不少来回。