数据库等延迟敏感场景应禁用透明大页(thp),因其后台khugepaged进程强制合并物理页,与高频内存分配/扫描冲突,引发锁竞争、tlb刷新及周期性卡顿;须设为never并验证anonhugepages为0及smaps无2048kb页。

强开透明大页(THP)在堆内存大范围扫描时引发卡顿,根本原因不在“用了大页”,而在于其后台自动合并机制与扫描行为触发的内存压力模式发生了恶性耦合。这不是配置错误,而是机制冲突。
1. 卡顿源头:khugepaged 正在后台强行压缩内存
当 THP 设置为 always,内核不会直接分配 2MB 大页给 malloc/new,而是先按 4KB 小页分配。随后,后台守护进程 khugepaged 持续扫描物理内存,寻找连续、空闲、同属一个 NUMA 节点的 512 个 4KB 页——一旦凑齐,就发起一次内存压缩(compaction)操作,把它们“捏”成一个 2MB 大页。
这个过程需要:
- 锁定相关内存区域(阻塞其他内存分配)
- 迁移正在使用的页(触发 page migration)
- 更新多级页表(涉及 TLB flush)
- 可能唤醒 kswapd 回收内存以腾出连续空间
整套动作是同步、高优先级、且不可中断的——它会抢占 CPU 时间片,造成数十毫秒到几百毫秒的延迟毛刺。
2. 堆扫描如何精准触发这个雷区
Java CMS/G1 GC、Go 的 mark-sweep、或 C++ 自定义内存池的 full sweep,本质都是对整个堆做线性遍历+读取标记位。这种行为具有两个致命特征:
- 高内存带宽占用:持续读取大量不相邻的虚拟地址,导致 TLB 快速失效,频繁触发 page walk;
-
隐式内存压力信号:扫描本身虽不分配新页,但会访问大量冷页,激活 inactive LRU 链表,让内核误判“内存即将不足”,从而加速
khugepaged扫描和 compaction 尝试。
结果就是:GC 线程越努力扫描,khugepaged 越疯狂压缩,CPU 在 GC 和 compaction 之间反复横跳,系统响应停滞。
3. 为什么“看起来内存充足”却仍卡死
关键在于连续物理内存不足,而非总内存不足。
- 堆内存长期使用后必然碎片化,物理页分散在不同内存块;
- khugepaged 要求 512 个 4KB 页必须物理连续,哪怕系统还有 20GB 空闲内存,只要没有一块 2MB 的连续空闲物理区,它就会不断尝试 compaction;
- 每次失败的 compaction 都会留下锁竞争、TLB 刷新、链表遍历等开销,累积成可观测的卡顿。
4. 验证与定位方法
不需要猜,用三行命令即可确认:
# 查看是否启用 THP 及当前状态 cat /sys/kernel/mm/transparent_hugepage/enabled <h1>查看 khugepaged 是否活跃、有无失败记录</h1><p>grep -i "huge|compact" /proc/vmstat</p><h1>实时观察 compaction 延迟(单位:微秒)</h1><p>cat /proc/sys/vm/compact_*</p>
若 pgmajfault 或 compact_stall 数值在堆扫描期间突增,基本可断定是 THP 后台机制在捣鬼。










