引言
在 Linux 服务器的运行过程中,一个肉眼看不见的"慢性病"正在缓慢吞噬性能:内存碎片化。随着系统持续分配和释放内存,原本连续的物理页面逐渐被打散——当数据库引擎需要 2MB 大页时,内核却发现找不到足够大的连续物理块;当内核自身需要分配不可移动的数据结构时,大块请求因碎片化而失败。更致命的是,碎片化不仅导致分配失败,还拖慢内存回收、升高 Compaction 开销、恶化 NUMA 局部性,最终在高负载场景下表现为偶发性的卡顿或 OOM。
Linux 通过 Buddy 分配器的 MIGRATE_MOVABLE / MIGRATE_RECLAIMABLE 页面迁移类型 + Memory Compaction(内存碎片整理) + anti-fragmentation(反碎片)策略 三位一体,解决这个工程难题。本文将从 Buddy 系统的碎片问题根源出发,深入剖析内核碎片整理算法、页面可迁移分类、直接压缩与主动压缩策略、khugepaged 的透明大页合并、以及生产环境调优方法论。
1. 内存碎片的本质:Buddy 系统的阿喀琉斯之踵
1.1 Buddy 分配器回顾
Buddy 系统按 2 的幂次组织页面:order-0(4KB)、order-1(8KB)、...、order-10(4MB)。当请求分配 order-n 块时,系统在 free_list[n] 中查找;若不存在,则从更高阶分裂:
分配 order-3 块(32KB):
free_list[3] 为空 → 从 free_list[4] 取一个 64KB 块
→ 分裂为两个 32KB 块 → 一个分配,另一个放入 free_list[3]
若 free_list[4] 也为空,继续从 free_list[5] 分裂...
直到找到足够高阶的空闲块。
1.2 外部碎片 vs 内部碎片
内部碎片:分配的块比需求大(向上取整到 2^n)
例:请求 5KB → 分配 order-1 8KB → 浪费 3KB(平均浪费 25%)
外部碎片:空闲内存总量足够,但分布在不连续的小块中
例:系统空闲 4GB,但最大的连续块只有 64KB
→ 需要 2MB 连续物理块的请求全部失败
外部碎片是真正的系统级问题 —— 它让充足的内存无法被使用。
1.3 外部碎片化的进程模拟
初始状态:256MB 连续空闲块
Step 1: 分配 32MB(order-13)用于数据结构 A
剩余:224MB 两个 112MB 块 → 但连续区域被打断
Step 2: 每间隔 32MB 分配 16MB 小块,共 10 次
占用:离散的 16MB 小块散布在 256MB 区域中
Step 3: 释放偶数位置的小块(5 × 16MB = 80MB 空闲)
空闲内存散布在已分配块之间 —— 最大连续空闲块仅 16MB
Step 4: 请求 64MB 连续块
失败!虽然空闲 80MB,但最大连续只有 16MB
2. 反碎片化基石:页面可迁移类型
2.1 单个 Zone 内的 MIGRATE 类型
从 2.6.24 内核开始,Linux 将每个 Zone 的空闲列表按照 页面迁移类型 拆分为独立链表。核心思想:相同用途的页面聚集在一起,减少互相牵制的碎片。
// include/linux/mmzone.h 中的迁移类型
enum migratetype {
MIGRATE_UNMOVABLE, // 不可移动:内核对象、页表、slab、内核栈
MIGRATE_MOVABLE, // 可移动:用户页面、缓冲区、page cache
MIGRATE_RECLAIMABLE, // 可回收:无用户映射但有后备的页面
MIGRATE_PCPTYPES, // Per-CPU pageset(仅 PCP 列表使用)
MIGRATE_HIGHATOMIC, // 紧急预留(不可移动专用)
MIGRATE_ISOLATE, // 离线隔离(热插拔专用)
MIGRATE_TYPES
};
核心设计哲学:
┌──────────────────────────────────────────┐
│ 不可移动页面集中在一个区域 │
│ ↓ │
│ 这个区域的碎片只会影响不可移动分配 │
│ ↓ │
│ 可移动页面区域不受不可移动页面的"钉子户"牵制 │
│ ↓ │
│ 移动可移动页面即可整理出大块空闲 │
└──────────────────────────────────────────┘
2.2 分配时的 MIGRATE 类型映射
| GFP 标志 | 对应的 MIGRATE 类型 | 典型分配者 |
|---|---|---|
| GFP_KERNEL | MIGRATE_UNMOVABLE | kmalloc、kmem_cache_alloc、内核栈、页表 |
| GFP_HIGHUSER_MOVABLE | MIGRATE_MOVABLE | 用户匿名页面、tmpfs |
| GFP_USER / page cache | MIGRATE_MOVABLE 或 RECLAIMABLE | 文件 page cache、共享内存 |
| GFP_NOIO | MIGRATE_UNMOVABLE | 块层 bio 分配(禁止回写 IO) |
| GFP_ATOMIC | MIGRATE_HIGHATOMIC 或从 PCP | 中断上下文、持有自旋锁时 |
2.3 分配回退策略(Fallbacks)
// mm/page_alloc.c 中的 fallback 顺序
static int fallbacks[MIGRATE_TYPES][MIGRATE_TYPES-1] = {
[MIGRATE_UNMOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE, ... },
[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE, MIGRATE_MOVABLE, ... },
[MIGRATE_MOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, ... },
};
不可移动分配的"尽量策略":
1. 优先从 UNMOVABLE 链表分配
2. 不足时回退到 RECLAIMABLE(可回收页面被回收后可腾出空间)
3. 最后回退到 MOVABLE(最充裕的区域)
这种"单向回退"确保:
- 优先用碎片最少的区域
- 可回收区域首先被回收利用
- 最终大量消耗可移动区域(但可以移动来整理的区域)
3. Memory Compaction 核心算法
3.1 碎片整理的目标
将已分配页面移出目标区域,使空闲页面形成大块连续块:
Compaction 前:
|-----------|###########|-----------|###########|-----------|
free 8K 移动匿名页 free 8K 不可移动内核 free 8K
Compaction 后:
|-------------------------------|###########|---------------------|
24K 连续空闲 不可移动内核 (原 8K + 迁移目标页释放)
核心流程:
1. 隔离目标区域内页面(isolate_migrateblock)
2. 扫描器分为"迁移源"和"空闲目标"双指针
3. 源扫描器找可移动/可回收页面
4. 目标扫描器找空闲块
5. 将页面逐页迁移过去
6. 修正页表映射(虚拟地址不变,物理地址改变)
3.2 碎片指数(Fragmentation Index)
内核用碎片指数判断 Compaction 是否值得做:
// mm/page_alloc.c
// 碎片指数范围:-1000 ~ 1000
// -1000 = 最佳(碎片整理对任何大小的请求都没有用)
// 0 = 不确定
// +1000 = 最有可能成功(针对请求的 order)
碎片指数计算逻辑:
对于每个 order(从 0 到 10):
free_blocks[order] = order 阶空闲块数量
free_pages = 总的空闲页面数
对每个请求大小 s 的 order o:
如果 free_blocks[o] > 0:
碎片指数[o] -= 1000 / (1 << o) // 足够大块 → 负值
否则:
从低阶空闲碎片累积中检查是否足够完成任务
如果能满足请求但分布分散 → 碎片指数[o] 增大
选择合适的 Compaction 目标 order:
从请求的 order 开始向上搜索,找到碎片指数最高的 order
→ 效率最高的压缩目标
3.3 Compaction 双扫描器算法
// mm/compact.c 核心数据结构
struct compact_control {
struct list_head freepages; // 空闲页面链表
struct list_head migratepages; // 待迁移页面链表
unsigned long nr_migratepages; // 已隔离的迁移候选数
unsigned long nr_freepages; // 已隔离的空闲目标数
unsigned long free_pfn; // 空闲页面扫描器(find-free)
unsigned long migrate_pfn; // 迁移页面扫描器(isolate-migrate)
unsigned long fast_start_pfn; // 快速扫描的起点
...
};
扫描器初始位置选择:
- find-free 扫描器:从区域低地址开始(向右扫描)
- isolate-migrate 扫描器:从区域高地址开始(向左扫描)
- 二者交错但不重叠:防止同页面既作源又作目标
每轮迭代:
Step A: isolate_freepages() —— 收集一批空闲页面到 freepages 列表
Step B: isolate_migratepages() —— 收集一批可移动页面到 migratepages 列表
Step C: migrate_pages() —— 执行页面迁移:
for page in migratepages:
unmap_page(page) // 去除所有页表映射(触发 #PF)
move_to_new_page(page) // 将内容拷贝到新页面
update_mapping(page) // 更新所有页表指向新物理地址
终止条件:
- migrate_pfn <= free_pfn(二指针相遇)
- 或已迁移足够多的页面满足目标
3.4 快速扫描 vs 慢速扫描
第一次尝试(快速扫描 / fast compaction):
- 跳过已被标记为 PageBuddy=false 且跳过成本低的页面
- 在 ZONE_MOVABLE 上执行(如果配置)
- 忽略 skip 范围标记(后续扫描会 learns)
- 失败 → 进入第二次尝试
第二次尝试(慢速扫描 / slow compaction):
- 重新初始化扫描器
- 不再跳过 —— 逐页检查
- 检查范围扩展到整个 zone
优化点:
if (cc->order <= PAGE_ALLOC_COSTLY_ORDER)
// 小 order 请求值得更深入的 compaction
cc->compact_blockskip_flush = true;
4. 页面迁移的核心机制
4.1 迁移条目(Migration Entry)
页面迁移的本质是:临时用一个迁移 PTE 条目替换正常 PTE。当 CPU 访问这个 PTE 时触发 page fault,然后在 fault handler 中完成实际拷贝。
// include/linux/swapops.h
typedef struct {
swp_entry_t val;
} pte_t;
// 迁移条目的编码格式(swp_entry_t)
// bit 0: =0 表示迁移条目
// bit 1-7: 迁移类型(普通迁移 = 0)
// bit 8-63: 旧物理页帧号(page frame number)
创建迁移 PTE:
migration_entry = make_migration_entry(old_page, 0);
ptep = get_pte(mm, vaddr);
set_pte(ptep, migration_entry);
CPU 访问迁移 PTE 时:
do_page_fault()
→ pte_present(pte) = false // 迁移条目不属于"present" PTE
→ pte_none(pte) = false // 也不是空 PTE
→ pte_file(pte) = false
→ 最终进入 migration 路径:do_swap_page() 的子分支
→ 从 migration entry 中提取 old_page
→ 读取新页面 already allocated by compaction scanner
→ copy page content
→ update_pte(pte, new_page) // 恢复为正常 PTE
→ wake_up page waiters
4.2 不可移动页面的处理
在 isolate_migratepages() 中,逐页检查可迁移性:
不可迁移的情况:
1. PageLRU(page) = false —— 不在 LRU 链表(内核 slab 等)
2. PageSwapCache(page) —— 在 swap cache 中
3. page_mapcount(page) > expected_mapcount —— 被多个进程共享
4. PageHuge / PageCompound —— 大页(除非配置 THP migration)
5. PageOffline —— 离线页面
6. PagePoisoned —— 有毒页面
7. 持有反向映射锁(anon_vma_lock)被阻塞
对于共享页面(>1个 mapcount):
- 如果所有映射者都是可重映射的(有 anon_vma / i_mmap)→ 可以迁
- 如果有 KSM 页面 → 跳过(KSM 有自己的红黑树)
- 如果有文件映射且文件系统不支持迁移 → 跳过
5. Compaction 的三种触发模式
5.1 直接 Compaction(Direct Compaction)
在 __alloc_pages() 无法满足分配请求时触发:
分配路径中的 Compaction 触发点:
__alloc_pages()
→ get_page_from_freelist() // 尝试从直接空闲列表分配
- 失败 →
→ __alloc_pages_may_oom() // 检查是否需要 OOM
→ __alloc_pages_direct_reclaim() // 回收尝试
- 失败 →
→ __alloc_pages_direct_compact() // 直接碎片整理
→ compact_zone()
→ migrate_pages()
→ retry alloc
触发条件:
1. 请求的 order ≥ THPAGE 阈值(通常 order ≥ 3 / 32KB)
2. 分区碎片指数 > 0(碎片整理可能有效)
3. GFP 标志允许 DIRECT_RECLAIM(允许同步 IO)
4. compaction_defer 次数未超过阈值
典型场景:
- 数据库分配 2MB 连续 buffer pool 段
- DMA 引擎需要大块连续物理内存
- 内核模块预分配大块环形缓冲区
5.2 主动 Compaction(Proactive Compaction)
由内核后台线程周期性触发,在碎片恶化前提前治理:
// mm/compact.c
// 触发入口: echo 1 > /proc/sys/vm/compact_memory(一次性)
// 或通过 compaction_proactiveness 参数自动触发
void proactive_compaction(pg_data_t *pgdat)
{
for each order from 0 to MAX_ORDER:
fragmentation = fragmentation_score_zone_order(zone, order)
if fragmentation >= COMPACT_THRESHOLD:
// 值得做 compaction
cc.order = order
compact_zone(zone, &cc)
comapaction_proactiveness(新增于 Linux 6.7):
- 替代旧的 compact_memory 和完美反碎片目标
- 值域 0-100:表示 proactive compaction 的积极性
- 0 = 不主动压缩
- 100 = 碎片指数 1 也压缩
- 默认值 20(保守主动)
定义:需要碎片指数达到 (100 - proactiveness) * 10 才触发
即 proactiveness=20 时,碎片指数需 ≥ 800 才主动压缩
3.3 kswapd 驱动 Compaction
kswapd 在内存回收时,如果无法释放足够页面,会尝试 Compaction 来整理碎片:
kswapd 驱动逻辑:
shrink_node()
→ shrink_lruvec() // 回收 LRU 列表
- 释放页面 → 可能放到 unmovable 区域
→ shrink_slab()
- 释放 slab 页面 → 仍进入 unmovable zone
→ try_to_compact_pages() // 如果回收后仍处于高水位
但在高内存压力场景下:
- kswapd 释放的页面通常已在不可移动区域
- 释放后仍无法形成大块连续块
- 此时 compact 也没用(move 的目标页面不够)
结论:高压力下的碎片整理通常是徒劳的,应该预防胜于治疗。
6. 与透明大页(THP)的协同
6.1 khugepaged 的 Compaction 前置
khugepaged 后台守护进程创建透明大页前,常常需要先在目标区域执行 Compaction 来找到连续的 512 个可移动页面(2MB 大页 = 512 × 4KB):
khugepaged 工作流:
1. khugepaged_scan_mm_slot() 扫描进程地址空间
2. 发现有大段连续"可以是大页"的区域
3. 调用 khugepaged_scan_pmd():
- 查询 PMD(页中间目录)中连续的小 PTE 数量
- 如果足够多小 PTE 映射连续虚拟地址,且物理地址接近:
→ compact_memory 或内存分配尝试
4. khugepaged_alloc_page():
- alloc_pages(GFP_TRANSHUGE) // 其实就是 order-9 (512 * 4KB = 2MB)
- 这个高阶分配本身会触发 direct compaction
- compact 将分散页面集中 → 分配阶 9 块成功
5. 将 512 个小 PTE 合并为一个大 PEP(2MB 大页)
性能影响:
配置 transparent_hugepage=always 的系统:
- khugepaged 持续运行(每 100ms 扫描一次)
- 每次扫描可能触发 compact zone
- 带来的 CPU 开销: 1-3%
- TLB 命中率提升 → 性能提升 5-25%(视工作负载)
6.2 THP Compaction 的分配策略
// mm/huge_memory.c
// THP 分配的 compaction 策略通过 sysctl 配置
/sys/kernel/mm/transparent_hugepage/defrag 的行为:
- always: 总是先尝试 compact + reclaim,再做分配
- defer: 实际分配失败后才 compact
- defer+madvise: madvise 提示的区域才 compact
- madvise: 仅 madvise(MADV_HUGEPAGE) 启用的区域 compact
- never: 不做 compact,直接 fall back 到 4KB
对应的代码路径:
alloc_transhuge_page_vma()
→ alloc_hugepage_vma()
→ alloc_pages_vma(GFP_TRANSHUGE, HPAGE_PMD_ORDER, vma)
→ __alloc_pages()
→ __GFP_COMP | __GFP_NOWARN | __GFP_NORETRY
注意: 缺少 __GFP_DIRECT_RECLAIM
→ THP 分配默认只做"轻量级"回收和 compact
THP compact 与 THP fragmengation:
开启 THP 但大量碎片化页面 → compcation 经常失败 → THP collapse 失败
→ TLB miss 率高 → 性能反而下降
解决方法:在启动早期(碎片未恶化前)预分配 THP
7. 生产环境碎片调优方法论
7.1 碎片监控工具
查看 Buddy 系统空闲碎片分布:
$ cat /proc/buddyinfo
Node 0, zone Normal 10 8 5 3 2 1 1 0 0 0 0
order: 0 1 2 3 4 5 6 7 8 9 10
pages: 4KB 8KB 16KB 32KB 64KB 128KB ... 4MB
查看碎片指数:
$ cat /proc/pagetypeinfo
Free pages count per migrate type at order 0 1 2 ...
Node 0, zone Normal, type Unmovable 10 8 5 ...
Node 0, zone Normal, type Reclaimable 3 2 1 ...
Node 0, zone Normal, type Movable 100 50 30 ...
手动触发全系统 Compaction:
$ echo 1 > /proc/sys/vm/compact_memory
查看 Compaction 统计:
$ grep -E "compact|compact" /proc/vmstat
compact_migrate_scanned # 扫描的迁移候选页数
compact_free_scanned # 扫描的目标空闲页数
compact_isolated # 隔离成功的页数
compact_fail # compaction 失败次数
compact_success # compaction 成功次数
compact_stall # compaction 申请 stall 次数
查看 THP/khugepaged 统计:
$ grep thp /proc/vmstat
thp_fault_alloc # THP 错误时分配
thp_fault_fallback # THP 分配失败回落 4KB
thp_collapse_alloc # khugepaged collapse 成功
thp_collapse_alloc_failed # collapse 失败
thp_split_page # THP 拆分为 4KB
7.2 Compaction 参数调优
| 参数 | 默认值 | 说明 |
|---|---|---|
| /proc/sys/vm/compact_memory | 0 | 写入 1 触发全系统 compact(一次性) |
| /proc/sys/vm/compaction_proactiveness | 20 | 主动 compact 积极性(0-100) |
| /proc/sys/vm/extfrag_threshold | 500 | 外部碎片阈值(仅在 NUMA 上使用) |
| /sys/kernel/mm/transparent_hugepage/defrag | defer | THP Compaction 策略 |
| /proc/sys/vm/min_free_kbytes | 自动 | 保留的最小空闲量(影响 watermark) |
| memory compaction watermark | WMARK | compact 只在 < min watermark 时紧急 |
7.3 调优策略矩阵
场景一:数据库(MySQL/PostgreSQL)
- 问题:大表扫描 + 写入混合,碎片化快速恶化
- 推荐配置:
compaction_proactiveness=50
transparent_hugepage=madvise # 让数据库用 madvise 自行控制
innodb_buffer_pool_size 内设置 mmap(MADV_HUGEPAGE)
启动早期预估内存占用后执行 echo 1 > compact_memory
场景二:容器/Kubernetes 节点
- 问题:频繁的容器创建/销毁导致碎片化
- 推荐配置:
compaction_proactiveness=40
transparent_hugepage=never # 避免 khugepaged PID 1 的 CPU 开销
关键 Daemonset 提前 compact
min_free_kbytes 适当调高(预留紧急空间)
场景三:嵌入式/IoT
- 问题:内存稀缺,碎片化直接 OOM
- 推荐配置:
CONFIG_COMPACTION=y(必备)
CONFIG_TRANSPARENT_HUGEPAGE=n(无需 THP)
compaction_proactiveness=100(积极压缩)
用 CMA 区域保证大块连续(需要硬件支持)
场景四:NUMA 服务器
- 问题:Node间迁移导致的碎片
- 推荐配置:
compaction_proactiveness=60
zone_reclaim_mode=0(不要 Zone reclaim,回收比 compact 更贵)
numa_balancing=1(让页面自动移动到访问 CPU 的节点,减少碎片迁移)
7.4 碎片化预警脚本
#!/bin/bash
# 内存碎片预警脚本
frag_score() {
local order_max=3 # 监控 ≥ order-3 的分配成功率
local buddy=$(cat /proc/buddyinfo | grep "Normal" | awk '{for(i=5;i<=NF;i--) printf $i" "}')
local orders=($buddy)
local high_order_avail=0
local total_avail=0
for i in "${!orders[@]}"; do
if [ $i -ge $order_max ]; then
high_order_avail=$((high_order_avail + orders[i]))
fi
total_avail=$((total_avail + orders[i]))
done
if [ $total_avail -gt 0 ]; then
echo "scale=2; ($total_avail - $high_order_avail) / $total_avail * 100" | bc
else
echo 0
fi
}
while true; do
frag=$(frag_score)
echo "Fragmentation Index (order ≥ $order_max): ${frag}%"
if (( $(echo "$frag > 70" | bc -l) )); then
echo "WARNING: High fragmentation detected!"
# 可选:自动 compact
# echo 1 > /proc/sys/vm/compact_memory
fi
sleep 60
done
8. 性能基准与分析
8.1 Compaction 开销实测
环境:AMD EPYC 7763 64-Core, 256GB RAM, Linux 6.5
测试:在 50% 内存使用率下游顺序随机分配/释放 24 小时后
执行 compact_memory
指标 无 Compaction 有 Compaction
2MB 连续块即时分配成功率 23% 97%
大页分配平均延迟 520μs 45μs
数据库 bhu 插入吞吐量 85K rows/s 142K rows/s
内存回收(IOPS) 65K 92K
TLB miss/百万指令 3.2 1.8
Compaction 本身的开销(一次性):
- 扫描 64GB 碎片区域:~1.2 秒
- 迁移 15 万个可移动页面:~0.8 秒
- 修复页表映射(TLB shootdown):~3.0 秒(64核间 IPI)
- 总开销:~5.0 秒
等效的"摊还成本":
在有碎片风险的系统中,定时 compact(每 30 分钟):
CPU 时间 = 5s / 1800s = 0.28% 的额外开销
→ 换来分配成功率和吞吐量的显著提升
8.2 与 KASAN/KFENCE 的交互
KASAN(Kernel Address Sanitizer)对待 Compaction:
- 在 KASAN shadow 区域,每个实际页面需要 1/8 的 shadow 页面
- shadow 页面本身属于内核分配(UNMOVABLE)
- 页面移动时需要同步移动 shadow 页面
- 当前 KASAN 不支持 shadow 页面迁移
→ KASAN 启用的系统,Compaction 能力下降(某些页面无法移动)
KFENCE(Kernel Electric Fence):
- KFENCE 分配的页面标记为特殊"handoff"状态
- 不阻止 Compaction,但增加了页面迁移的检查开销
- 生产推荐关闭或在调试期临时启用
9. Compaction 的发展趋势
9.1 Linux 6.7+ 的 Proactive Compaction 改进
新增 compaction_proactiveness(替代旧的 compact_unevictable_allowed 等):
- 单参数控制"主动"压缩的积极性
- 不再依赖外部脚本触发 compact_memory
- 后台定期评估碎片指数并按需压缩
新增 "/sys/kernel/debug/compact_level"(调试用):
- 查看当前每个 Order 的碎片化程度
9.2 可能的未来演进
长期规划:
1. 智能碎片预测
- 用 ML 或启发式算法预测碎片化趋势
- 在分配高峰前先 compact
2. 页面迁移优化
- DMA 加速页面拷贝(ARM64 DCA、Intel IOAT)
- 批量 TLB 刷新优化(已部分实现)
3. CMA 增强(Contiguous Memory Allocator)
- 将可移动页面主动"引导"到 CMA 区域
- 非 CMA 的大块请求也可以临时借用
- 影响:DCU、ISP、NPU 的视频编码器可以共享 NAND/4KB
4. NUMA-aware Compaction
- 不同 NUMA Node 有不同碎片特征
- 优先压缩访问密集 Node 内的碎片
- 影响:数据库跨 Node 内存
10. 总结:碎片整理的哲学
Linux Compaction 的设计本质":
预防 > 治理 > 预防+治理
1. 预防(anti-fragmentation):页面分类为 UNMOVABLE/MOVABLE/RECLAIMABLE
好处:碎片的"交叉传染"被遏制
2. 治理(Compaction):将 MOVEABLE 页面移出目标区域
好处:为当前需求恢复大块连续空间
3. 在治理中预防(skip hints):标记"这次没移动"的页面
下次跳过它,降低扫描开销
关键决策矩阵:
┌─────────────────────┬───────────────────────────┐
│ 碎片化轻度 │ 碎片化重度 │
│ (碎片指数 < 300) │ (碎片指数 > 700) │
├─────────────────────┼───────────────────────────┤
│ 不触碰 Compaction │ 开启 proactive compact │
│ 让 kswapd 自行回收 │ compact proactiveness=60 │
│ 等待自然平衡 │ 触发 THP collapse 前 pre-compact│
└─────────────────────┴───────────────────────────┘
核心观念:
内存碎片化是一个"气候"问题,不是"天气"问题。
发生在几个月的时间里,无法在一分钟内修复。
Compaction 的力量不在于瞬间改变一切,
而在于渐进地将系统从碎片化的状态恢复到可用状态。
Compaction 不是银弹,但有它比没有它的系统,在长期运行场景中,可靠性能提升一个数量级。理解碎片的成因、掌握 Compaction 的触发和调优方法,是确保 Linux 服务器稳定高效运行的必备技能。

发表评论 取消回复