首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
Linux内存碎片化与主动规整:从原理到调优实践
📅 2026/10/23 3:18:45
✍️ 爱科研究院
👁 阅读 3,247
内存明明还剩一大半申请一块 2M 的连续内存却直接失败free 显示还有几个 G 空闲可进程一启动就卡顿页面回收一遍又一遍问题却依然存在。做数据库、跑 Java 服务、搞虚拟化集群的朋友大概率都撞见过这种诡异现象。第一次遇到时十有八九会怀疑是不是内存泄漏来回查了一圈才发现真正的元凶是外部碎片化。而主动内存规整proactive compaction这套机制就是内核专门为对抗外部碎片化设计的“闲时整理”方案。这篇内容我打算从碎片产生的原因讲起把 proactive compact 的触发机制、关键参数、观测方法以及调优经验一次性理清楚适合想深入理解 Linux 内存管理的内核开发者、系统调优工程师和运维同学参考。1. 先从“内存够用却分配失败”说起外部碎片化到底怎么发生的1.1 伙伴系统的最小分配单位与“大块连续内存请求”Linux 的物理内存管理以 page通常 4K为基本单位伙伴系统buddy system把所有空闲页按 2 的幂次组合成不同阶order的块。order-0 就是单个 4K 页order-1 是 8Korder-3 是 32Korder-9 是 2Morder-10 是 4M。平时我们写用户态程序申请内存大多走的是 order-0 的页虚拟内存连续就行物理内存是否连续并不重要。但内核里有很多场景必须拿到“物理连续”的一大块内存页表分配、DMA 缓冲区、透明大页THP的分配、显式映射的 hugetlbfs 大页等等。尤其透明大页普及之后khugepaged 会尝试把进程地址空间里满足条件的虚拟内存折叠成 2M 的 THP底层就需要在物理内存里找到一块连续的 order-9 页块。问题就出在这伙伴系统总共的空闲页数量可能完全充足但它们分散在各个孤立的空洞里最大的一块连续内存撑死只有 order-4、order-5。这时想申请 order-9 的块内核只能触发内存规整把分散的小页搬运到一起、腾出连续的大块如果搬运也解决不了最终就是分配失败。1.2 碎片产生的根源不同“迁移属性”的页面交错布局为了理解碎片为什么会产生要先认识伙伴系统里页面迁移属性的分类。内核把物理页按迁移属性分成三大类不可移动页MIGRATE_UNMOVABLE、可回收页MIGRATE_RECLAIMABLE、可移动页MIGRATE_MOVABLE。不可移动页包括内核分配的一些核心对象、页表、某些驱动申请的页它们一旦被放到某个物理地址就不能再搬走了。可移动页主要指用户态进程的匿名页、页缓存等这类页可以通过页表重新映射把内容搬到另一个物理页上原页面就能释放出来。内核在分配页面时会尽量按迁移属性把同类页面聚在一起每个 pageblock通常是 2M 的物理块即 order-9尽量保持同一种迁移类型。但实际情况不可能那么理想。驱动申请一个不可移动页时如果刚好填进了可移动页区域的某个 pageblock 中间这个 pageblock 就相当于被“插了一根钉子”。内核页面分配和回收的生命周期又不一致有的页长期驻留有的页频繁换进换出时间一长pageblock 里的空闲块就被切得七零八落形成大量“有零星空位但没有连续大块”的状态。我这里经常用一个仓库的类比来解释把物理内存想象成一个仓库每个 pageblock 是一个货架货架上有若干标准位置。有人不断往货架上放各种规格的箱子有些箱子要长期占用位置有些箱子随时会被拿走。当你想腾出一整排完整的空货架来放一个超大号箱子时发现每个货架都被零星占着看似仓库面积还有很多但就是凑不出一整排。内存外部碎片化就是这个感觉它不是内存“少了”而是“乱了”。2. 主动内存规整proactive compact的设计思路与核心机制2.1 传统被动规整与主动规整的本质区别内核在主动内存规整出现之前并不是没有处理外部碎片的机制。传统内存规整memory compaction早就存在它做的事情是扫描 pageblock把可移动页迁移到别处腾出连续的大块页面。但传统的触发方式偏“被动”主要有两条路径第一直接内存规整direct compaction。进程在内核分配路径上申请高阶页面比如 order-9发现对应 zone 的空闲页不足并且碎片程度比较严重时当前进程会被迫停下来同步执行一次 compaction把页面腾出来再继续分配。这种做法的代价非常直观申请内存的进程自己要承担迁移页面的开销延迟可能高达几毫秒甚至更久。在数据库、存储这类对时延敏感的服务里这样的长尾延迟非常致命。第二kswapd 后台回收触发下的规整。kswapd 本身主要做页面回收在回收过程中如果发现某些 zone 的高阶分配长期得不到满足也会唤醒 kcompactd 尝试规整但这仍然是“已经出了问题之后再去补救”。主动内存规整proactive compaction的思路则完全是反过来的不用等到分配路径上真的卡住而是由内核的后台线程周期性地评估整个 node 的内存碎片化程度一旦碎片分数超过预设阈值就提前把内存整理好以便后续的高阶内存请求可以“现成”地拿到连续内存不在关键时刻触发 direct compaction。我把这两者的区别理解为“闲时大扫除”和“客人来了才匆忙找干净房间”的区别。主动规整的理想状态是系统空闲时把碎片收拾干净等大页请求到来时直接从货架上搬走一个完整大箱子。2.2 关键参数 vm.compaction_proactiveness 与碎片评分逻辑主动内存规整并不是无限度地反复清扫那样 CPU 开销会失控。它需要一套策略来判断“现在的碎片程度是否值得整理”。这个判断机制的对外表现就是内核参数 vm.compaction_proactiveness。这个参数是一个 0 到 100 之间的百分比值默认是 20单位可以理解为“主动程度阈值”。0 表示完全关闭主动压缩只保留原有的被动触发路径100 表示系统非常激进地保持内存紧凑状态只要分数偏高就会立刻整理。内核的判断基础是一个 zoned 的碎片分数fragmentation score核心会结合两个维度来评估当前 zone 的空闲内存是否满足基本水位以及该 zone 在目标分配阶比如 order-9上的外部碎片化程度。我把分数简单理解为“如果需要立即分配一个大块连续内存有多大比例会因为碎片问题而失败”。如果这个分数高于 sysctl 配置的 proactiveness 阈值kcompactd 就会启动一次主动规整。这里有一个值得说的细节主动规整不是凭借单次评估就一股脑把整个 node 全部扫一遍。内核会先关注那些碎片化最严重的 node处理完一轮之后重新计算分数。一旦分数回落到阈值内就停下来睡觉避免空转消耗 CPU。因此调高这个参数带来的效果本质上是提高了 kcompactd 唤醒的敏感度和持续工作的时长。2.3 主动规整的执行细节与代价分析真正执行主动规整时kcompactd 会沿着 zone 的页面结构做扫描。其核心动作是在某个 pageblock 中发现足够数量的可移动页后先尝试把这个 pageblock 整体隔离isolate然后把其中可移动的页迁移到别的空闲 block 中最后释放原 pageblock使其变成一整块连续空闲区间。这里要特别注意的是页面迁移并不是“改个指针”就结束的。对匿名页来说迁移需要先创建新的页、把原页内容拷贝过去、更新页表和反向映射再释放旧页对页缓存来说还得考虑文件页是否被多个进程同时映射。整个流程涉及到大量的锁操作和 TLB 刷新开销并不小。所以主动规整的价值不在于免费的魔法而在于把高代价的迁移工作尽量安排在系统相对空闲的时候去做避免在内存分配的关键路径上“临时抱佛脚”。代价从“用户态可见的分配延迟”转移成了“后台 CPU 消耗”和“迁移期间短暂的页面锁定开销”。对绝大多数负载来说这种转移是划算的。3. kcompactd 是怎么“干活”的从唤醒到执行的主链路拆解3.1 kcompactd 线程的角色定位主动内存规整的实际执行者是 kcompactd 内核线程。这是一个每个 NUMA node 都会创建的后台线程名字一般是 kcompactd0、kcompactd1 这样的格式。它平时大部分时间处于睡眠状态被唤醒之后才会执行规整。唤醒 kcompactd 的路径不止主动规整这一条。传统内存规整的调用场景也需要它kswapd 在回收过程中发现某个 zone 的高阶页面极度短缺、khugepaged 在折叠 THP 前检测到碎片可能要阻碍折叠、以及直接分配路径上通过 wakeup_kcompactd 预先叫醒它这些都是唤醒来源。主动规整只是给 kcompactd 增加了一个“周期性自我唤醒”的驱动逻辑。如果非要用一句大白话总结 kcompactd 的角色它就是一个专门负责整理内存碎片的“专职保洁员”平时待命收到命令或者发现碎片达到警戒线后就干活。3.2 主动规整的循环判断碎片分数与水位检查在主动规整路径上kcompactd 的主流程大致是这样的被唤醒后遍历本 node 的所有 zone先检查当前 zone 是否需要主动规整。这里的判断包含两个关键门槛第一是水位门槛。如果一个 zone 的空闲内存连基本水位watermark都无法保证主动规整就不会强行执行因为规整本身需要消耗额外的空闲页作为迁移目标页内存本身都紧张的时候再去搞迁移只会加剧内存压力。这一点很重要很多初学者误以为内存越碎片越应该主动整理实际上内存极度紧张时应该交给回收机制去处理。第二是碎片分数门槛。系统会计算当前 zone 在目标阶上的外部碎片指数然后与 vm.compaction_proactiveness 的阈值比较。只有分数超过阈值才认为“现在值得整理”。内核会持续按这个节奏循环整理一轮、重新计算分数、如果分数依然超标就接着整理如果降到阈值以下就停止。这个循环不是无间隔的空转中间会伴随合理的睡眠延迟避免 CPU 被 kcompactd 无限占用。3.3 主动规整与其他内存机制的配合关系主动规整并不是一个孤立的功能它和 THP、khugepaged、kswapd 这几个机制都存在联动。THP 的分配路径中如果 zone 的高阶空闲块不足内核一方面可能触发 direct compaction另一方面也会唤醒 kcompactd。有了主动规整之后很多 THP 分配请求可以直接命中已经整理好的连续块而不用再临时抱佛脚。khugepaged 在折叠 THP 时同样受益它扫描进程地址空间找到适合折叠的区间后底层需要分配 order-9 页面。如果主动规整提前把内存整理紧凑了khugepaged 的折叠成功率会明显提升THP 的数量也会更稳定。和 kswapd 的配合则要复杂一点。kswapd 的主要职责是页面回收它回收完页面后可能会释放出不少空 pageblock但未必连续。主动规整可以接着把这些空 block 合并整理让它变成真正的大块连续区域。反过来如果回收都回收不出足够内存主动规整也会因为水位检查不通过而自动闭嘴不会在系统内存紧张时添乱。4. 调优实战从参数调整到效果验证的完整操作4.1 第一步先看清当前系统的碎片状态调优之前先要确定系统是否真的存在外部碎片问题。我在生产环境排查时习惯先看三个 proc 节点。第一个是 /proc/buddyinfo。它展示每个 zone 在不同 order 下的空闲页块数量。输出的每一列代表一个 order从 order-0 到 order-10 左右。字段值表示当前 zone 里该 order 的空闲块个数。如果某一列非常高、但紧随其后的大 order 列却很低说明大量空闲页分散在前面的小块里连续大块不足这就是碎片化比较明确的信号。第二个是 /proc/pagetypeinfo。它比 buddyinfo 更精细展示每个 zone 里不同迁移类型Unmovable、Reclaimable、Movable 等在不同 order 下的空闲块数量。通过这个文件可以看到不可移动页夹在可移动页 pageblock 中的情况。正常情况下 Movable 类型应该在高 order 段拥有较多空闲块如果 Unmovable 类在高 order 段也有不少说明不可移动页污染了连续区域。第三个是 /proc/vmstat 里 compact_ 开头的统计项。我常用的几个字段如下字段含义观测要点compact_stall分配路径上被阻塞执行规整的次数越高说明碎片影响越明显compact_success规整成功的次数与 stall 对比判断碎片可治理性compact_fail规整失败次数持续增长说明碎片难以解决compact_pages_moved成功迁移的页面总数反映规整工作量compact_pagemigrate_failed页面迁移失败的次数过高说明有页面迁移受阻查看命令很简单cat /proc/buddyinfo cat /proc/pagetypeinfo grep compact_ /proc/vmstat如果 compact_stall 很高、同时 buddyinfo 里 order-9 以上的空闲块几乎为 0就可以考虑启用或调高主动内存规整了。4.2 第二步调整 vm.compaction_proactiveness 参数查看当前值sysctl vm.compaction_proactiveness临时修改sysctl -w vm.compaction_proactiveness40把参数写入配置文件使其重启后依然生效echo vm.compaction_proactiveness 40 /etc/sysctl.conf sysctl -p我不建议一上来就把参数调得很高。主动规整毕竟是后台线程在消耗 CPU调的太激进内存碎片是被收拾干净了但 CPU 开销可能得不偿失。我的经验是先用默认值 20 观察一段时间如果 compact_stall 依然很高、THP 分配成功率低再以 10 为步长逐步上调到 30、40。如果系统里跑的是延迟敏感型服务还要同时关注 CPU usage 和 softirq 之类的基本指标确认主动规整没有挤占业务资源。如果考虑彻底关闭主动规整可以设置成 0。但这个操作只影响主动路径原有被动触发的内存规整依然存在该发生的 compact_stall 还是会发生只是把整理工作完全推给了分配路径。4.3 第三步用 vmstat 和 tracepoint 验证效果参数调整后过一段时间重新查看 compact_ 统计是否出现预期的变化。正常情况下主动规整生效后compact_pages_moved 会有明显增长compact_stall 的增速会放缓因为很多原本需要在分配路径上临时执行的规整工作被后台提前做掉了。想看主动规整到底跑了多少次可以用 tracepoint 抓 mm_compaction_kcompactd_sleep 和 mm_compaction_begin、mm_compaction_end 这一组事件。内核的 compaction 事件都能通过 ftrace 直接观测echo mm_compaction_begin /sys/kernel/tracing/set_event echo mm_compaction_end /sys/kernel/tracing/set_event echo mm_compaction_kcompactd_sleep /sys/kernel/tracing/set_event echo 1 /sys/kernel/tracing/tracing_on通过 trace 输出可以清楚看到 kcompactd 被唤醒的时间点、处理了哪个 zone、迁移了多少页面、最终是否成功。这是区分主动规整和被动规整最直观的方法也是排查“为什么调了参数没有效果”的第一利器。4.4 一个典型的调优场景虚构案例某个服务进程频繁申请 THP虚拟机监控发现 THP 分配失败次数一直在涨日志里也有明显的 direct compaction 长延迟。查 /proc/buddyinfo 后发现order-9 空闲块长期为 0但 order-0 到 order-4 的小块数量很多明显是典型的外部碎片。我再查 vm.compaction_proactiveness发现系统默认把它设为 0某些环境会主动关闭。把参数从 0 改成 30 之后观察了半个小时compact_pages_moved 稳步增长order-9 的空闲块开始出现。再过一段时间THP 分配失败次数明显下降业务侧的分配延迟尖刺也消失了。这个案例说明主动内存规整对“碎片化导致的高阶分配失败”这一类问题效果非常直接。5. 常见问题与排查技巧实录5.1 问题一调节参数后 CPU 飙高现象sysctl -w vm.compaction_proactiveness100 后kcompactd 线程 CPU 占用极高业务响应反而变差。原因参数调得太激进内核判定几乎所有 node 都“值得整理”kcompactd 进入持续工作状态。加上系统本身页面分配释放频繁刚整理完的 pageblock 很快又被新页面打散出现了“整理-碎片-再整理”的循环。排查思路查看 /proc/vmstat 的 compact_pages_moved 和 compact_pagemigrate_failed同时观察 kcompactd 线程的 CPU 占用。解决方法是把 proactiveness 降回一个合理区间比如 20 到 50或者接受 THP 分配的少量延迟避免后台整理消耗过大。如果业务对内存连续性的要求没那么高甚至可以考虑关闭 THP从源头上减少高阶分配需求这比强制让 kcompactd 疲于奔命要划算得多。5.2 问题二参数调整了但碎片分数始终很高现象主动规整一直触发但 /proc/buddyinfo 里高 order 空闲块依然很少compact_success 也不见增长。原因系统里存在大量不可移动页长期驻留它们像钉子一样钉在 pageblock 里导致可移动页就算全部迁走对应 pageblock 也没法变成完整的连续块。或者系统里大量页是短生命周期的刚迁移完又新增了大量碎片。排查思路用 /proc/pagetypeinfo 观察 Unmovable 类型的页面分布。如果不可移动页在多个 pageblock 里交错存在主动规整能做的事情就很有限。这种时候应该考虑从源头控制不可移动页的分配比如检查是否有驱动做了过量的内核对象分配、是不是有内存碎片化问题严重的模块在运行。给关键进程配置 cpuset/numa 绑核让不同内存特性的业务隔离开也是常见的辅助手段。5.3 问题三容器/内存控制组场景下效果不明显现象在一个资源受控的容器里系统级主动规整看起来没有减少容器内 THP 分配失败。原因主动内存规整的扫描是以物理内存 node 和 zone 为单位的它并不感知 cgroup 的边界。容器进程的页面可能分散在多个不同 pageblock 中但同一 pageblock 里的其他页面属于其他容器或宿主系统kcompactd 可以迁移容器页面却无法控制迁移后资源占用或其他容器的释放行为。因此主动规整对跨控制组的内存碎片治理效果有限。排查思路这类场景优先关注容器自身的透明大页策略把内存相对规整的业务放在同一个物理 NUMA node 上而不是依赖系统级主动规整来解决 cgroup 内的碎片问题。5.4 问题四kcompactd 唤醒频繁但迁移大量失败现象tracepoint 里看到 kcompactd 频繁醒来但 mm_compaction_end 显示 migrate_pages 失败数量很多。原因页面迁移失败往往是因为页被锁住、页正被写入磁盘、映射关系复杂导致反向映射更新太慢或者没有足够的空闲页可以容纳迁移过来的内容。在内存本身紧张、水位偏低时启动主动规整也容易遇到迁移目标页不足的问题。排查思路先看 /proc/vmstat 的 compact_pagemigrate_failed 增长趋势如果持续走高建议结合实时水位检查是否内存整体不足。同时可以配合使用 per-node 统计和 tracepoint 定位具体 zone再考虑是否把主动规整的分级判断条件放开一点避免 CPU 花在注定失败的迁移上。6. 聊一些主动内存规整的取舍和实战经验用了一段时间主动内存规整之后我的总体感觉是它不是一个需要经常动的参数但理解它的原理对排查高阶内存分配问题非常关键。把它当作“提前整理内存”的旋钮就好不要指望调大它就能解决所有内存性能问题更不要一上来就调到 80、100。碎片问题的根源往往是页面分配模式、不可移动页干扰、THP 使用策略这些更前置的因素主动规整只是在碎片已经出现之后把系统损失降到最低的一种补偿手段。另外提一个容易忽视的配合技巧如果你确定业务需要大量 THP 或 hugetlbfs除了调整 compaction_proactiveness还可以把 vm.zone_reclaim_mode 和 NUMA 策略同时考虑进去。对于可迁移性差的业务内存尽量让它们与那些频繁申请释放内存的进程分布在不同的 NUMA node 上这样可以从源头上降低跨 node 的迁移干扰。我在测试环境常用 cpuset 把两个业务绑到不同 node配合默认的 proactiveness 20内存碎片管理的效果已经很理想甚至不需要额外调参。最后再分享一个小技巧调试主动内存规整时别只盯着 proc 里的统计数字建议直接用 ftrace 的 mm_compaction_begin/mm_compaction_end 跟踪一轮完整的规整过程看清楚 kcompactd 从哪个 zone、哪个 order 开始处理迁移的页面到底来自哪些 pageblock。掌握这一类底层观测方法以后遇到任何内存碎片相关的问题你都能更快定位到是“碎片太严重”还是“主动规整没生效”也就能少走很多弯路。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/23 3:18:45
内存碎片隐形刺客:Linux主动内存规整原理与kcompactd调优
2026/10/23 3:13:45
Java通信录项目实战:基于SQLite与JDBC的增删改查实现
2026/10/23 3:13:45
KubeEdge实战:云原生边缘计算架构与最小集群部署
2026/10/23 7:09:10
嵌入式工程师如何让通宵查死机的技术价值被看见
2026/10/23 7:09:10
纯Java Swing实现俄罗斯方块:从数据结构到碰撞检测的完整实战
2026/10/23 7:09:10
单链表选择排序详解:数据交换实现与指针调试技巧
2026/10/23 7:09:10
MATLAB光纤色散仿真与链路预算:D/β2换算、SSFM和眼图Q值
2026/10/23 7:09:10
智能体工具系统落地指南:从工具定义到调度与错误处理
2026/10/23 7:04:10
Seedance 2.0人像动态短片全链路实战:从照片到成片的参数调优与避坑指南
2026/10/23 0:02:44
Verilog实现FPGA密码锁:矩阵键盘与状态机设计实战
2026/10/23 0:02:44
数据服务监控运维实战:从指标体系到告警治理与故障排查
2026/10/23 0:02:44
FreeRTOS任务通知实战详解:内核原理、API与避坑指南
2026/10/23 0:52:23
OpenResearch实战:Claude Code、Codex、OpenCode、Cursor 工作流搭建与故障排查
2026/10/22 17:15:21
知识管理核弹:用Obsidian构建第二大脑的工程化方案(TaoToken 统一 Key 接入版)
2026/10/23 0:52:11
金融AI落地指南:从调研报告到技术选型与工程实践
2026/10/22 5:06:15
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/22 2:07:18
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/21 19:01:08
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)