4. 内存回收概述:直接回收与后台回收、kswapd与direct reclaim、回收触发时机
内存回收是Linux内核内存管理子系统中最重要的动态调节机制之一。当系统物理内存不足以满足新分配请求时,内核必须通过回收已使用的内存页来腾出空间。理解回收的两种路径——后台回收(Background Reclaim)与直接回收(Direct Reclaim),以及它们对应的内核线程kswapd和direct reclaim路径,是掌握内存水位线调优的基础。
核心概念速览: 后台回收由kswapd内核线程异步执行,在内存压力达到阈值前提前回收;直接回收由分配路径同步触发,发生在内存严重不足时,会导致进程阻塞和延迟抖动。
4.1 两种回收路径:后台回收 vs 直接回收
Linux内核将内存回收分为两条独立的执行路径,它们在触发条件、执行上下文和性能影响上存在本质差异。
| 对比维度 | 后台回收(Background Reclaim) | 直接回收(Direct Reclaim) |
|---|---|---|
| 执行者 | kswapd内核线程(每个NUMA节点一个) | 发起内存分配的进程自身 |
| 触发时机 | 空闲内存低于pages_low水位线 | 空闲内存低于pages_min水位线 |
| 执行方式 | 异步、非阻塞、可睡眠 | 同步、阻塞、进程上下文 |
| 性能影响 | 低,后台线程平滑回收 | 高,分配延迟显著增加 |
| 适用场景 | 正常内存压力下的预回收 | 内存严重不足时的紧急回收 |
调优要点: 在容器化环境中,直接回收是导致应用尾延迟(tail latency)飙升的主要元凶。通过合理配置水位线参数,尽量让回收发生在kswapd后台路径中,是保障容器QoS的关键手段。
4.2 kswapd内核线程:后台回收的守护者
kswapd是内核为每个NUMA节点创建的一个内核线程,名称格式为kswapdN(N为节点编号)。它的职责是在内存压力尚未达到临界点之前,主动回收内存页,使空闲内存保持在pages_high水位线以上。
4.2.1 kswapd的工作流程
- 唤醒条件:当某个NUMA节点的空闲内存低于pages_low水位线时,内存分配路径会唤醒该节点的kswapd线程。
- 扫描与回收:kswapd遍历节点的LRU链表(活跃/不活跃页列表),按照优先级从低到高扫描,回收符合条件的页(如文件页、匿名页)。
- 停止条件:当空闲内存恢复到pages_high水位线以上时,kswapd进入睡眠状态,等待下一次唤醒。
- 平衡策略:kswapd会尽量在多个内存区域(zone)之间平衡回收压力,避免某个zone过度回收。
关键代码路径: kswapd的主循环位于
mm/vmscan.c 中的 kswapd() 函数。它调用 balance_pgdat() 执行实际的页面扫描与回收逻辑。
4.2.2 kswapd与水位线的交互
kswapd的行为完全由三个水位线驱动:
| 水位线 | 含义 | 对kswapd的影响 |
|---|---|---|
| pages_high | 目标水位线,表示内存充足 | 空闲内存达到此值 → kswapd停止回收 |
| pages_low | 低水位线,表示内存开始紧张 | 空闲内存低于此值 → 唤醒kswapd开始回收 |
| pages_min | 最小水位线,表示内存严重不足 | 空闲内存低于此值 → 触发direct reclaim |
4.3 Direct Reclaim:同步回收的紧急路径
当kswapd来不及回收足够的内存,或者内存分配请求过于紧急时,内核会进入直接回收(Direct Reclaim)路径。此时,发起内存分配的进程会同步阻塞在自己的分配路径上,直接执行页面回收操作,直到满足分配需求或回收失败。
4.3.1 Direct Reclaim的触发条件
- 水位线触发:分配路径检查发现空闲内存低于pages_min水位线。
- 原子分配失败:带有
GFP_ATOMIC标志的分配请求无法从紧急预留内存中满足。 - NUMA节点不平衡:目标节点的内存严重不足,无法通过跨节点分配缓解。
- 内存碎片化:连续物理内存不足,无法满足高阶分配请求。
4.3.2 Direct Reclaim的性能代价
| 性能影响维度 | 具体表现 | 典型延迟范围 |
|---|---|---|
| 分配延迟 | 进程在回收完成前无法继续执行 | 10μs ~ 10ms(取决于回收页数) |
| CPU消耗 | 进程上下文执行LRU扫描、页表操作 | 高,可能占满单核 |
| 锁竞争 | 多个进程同时进入direct reclaim时争抢LRU锁 | 加剧延迟抖动 |
| OOM风险 | 回收失败后可能触发OOM Killer | 进程被终止 |
容器环境警示: 在内存超卖(overcommit)的容器宿主机上,多个容器同时触发 Direct Reclaim 会导致全局性的性能抖动。监控
/proc/vmstat 中的 pgscan_direct 和 pgsteal_direct 计数器,可以量化直接回收的频率和严重程度。
4.4 回收触发时机:水位线驱动的决策机制
内核的内存分配路径在每次分配前都会检查当前空闲内存与水位线的关系,从而决定是否触发回收以及触发哪种回收路径。这个决策机制是内存回收的核心调度逻辑。
4.4.1 水位线计算与配置
水位线由内核在系统启动和内存热插拔时动态计算,主要基于以下参数:
- min_free_kbytes: 内核参数,直接决定pages_min的值。默认值约为物理内存的0.4%~1%。
- watermark_scale_factor: 控制pages_low和pages_high与pages_min的间距。默认值为10(表示间距为pages_min的10%)。
- watermark_boost_factor: 在高阶分配失败时临时提升水位线,减少碎片化影响。
计算公式(简化):
pages_min = min_free_kbytes / page_size
pages_low = pages_min + (pages_min * watermark_scale_factor / 100)
pages_high = pages_min + 2 * (pages_min * watermark_scale_factor / 100)
pages_min = min_free_kbytes / page_size
pages_low = pages_min + (pages_min * watermark_scale_factor / 100)
pages_high = pages_min + 2 * (pages_min * watermark_scale_factor / 100)
4.4.2 分配路径的决策流程
以下伪代码展示了内存分配路径中的水位线检查逻辑:
// 简化自 mm/page_alloc.c 中的 __alloc_pages()
struct page *__alloc_pages(gfp_t gfp_mask, unsigned int order) {
// 1. 尝试从伙伴系统分配(快速路径)
page = get_page_from_freelist(alloc_mask, order, ALLOC_WMARK_LOW);
if (page)
return page;
// 2. 唤醒 kswapd 后台回收
wake_all_kswapds(order, gfp_mask, ac);
// 3. 尝试使用最低水位线分配(允许少量低于 low)
page = get_page_from_freelist(alloc_mask, order, ALLOC_WMARK_MIN);
if (page)
return page;
// 4. 进入直接回收路径(慢速路径)
page = __alloc_pages_slowpath(gfp_mask, order, ac);
return page;
}
4.4.3 回收触发时机总结
| 触发条件 | 回收路径 | 分配行为 | 性能特征 |
|---|---|---|---|
| 空闲 > pages_high | 无回收 | 快速分配,直接返回 | 零延迟 |
| pages_low < 空闲 < pages_high | kswapd后台回收 | 分配成功,kswapd异步回收 | 低延迟,无阻塞 |
| pages_min < 空闲 < pages_low | kswapd + 轻度direct reclaim | 分配可能成功,但部分进程阻塞 | 中等延迟,抖动开始 |
| 空闲 < pages_min | Direct Reclaim | 分配阻塞,等待回收完成 | 高延迟,尾延迟飙升 |
| 回收失败 | OOM Killer | 终止进程释放内存 | 进程被杀死,业务中断 |
4.5 监控与诊断:如何判断回收路径是否健康
在实际生产环境中,通过内核统计接口可以实时监控回收路径的运行状态:
4.5.1 关键监控指标
| 监控文件 | 关键字段 | 含义 |
|---|---|---|
/proc/vmstat |
pgscan_direct |
直接回收的扫描页数(非零表示发生direct reclaim) |
/proc/vmstat |
pgsteal_direct |
直接回收成功回收的页数 |
/proc/vmstat |
pgscan_kswapd |
kswapd后台回收的扫描页数 |
/proc/vmstat |
allocstall |
分配路径进入慢速路径(direct reclaim)的次数 |
/proc/zoneinfo |
pages_min/pages_low/pages_high |
当前各zone的水位线配置值 |
快速诊断命令:
# 查看直接回收是否活跃grep -E "pgscan_direct|pgsteal_direct|allocstall" /proc/vmstat# 查看当前水位线配置grep -E "pages_min|pages_low|pages_high" /proc/zoneinfo
4.5.2 健康状态判断标准
- 健康:
pgscan_direct长期为0或极低,allocstall接近0。所有回收由kswapd完成。 - 亚健康:
pgscan_direct间歇性非零,allocstall缓慢增长。需要调整水位线或增加内存。 - 不健康:
pgscan_direct持续增长,allocstall快速上升。容器应用可能出现明显的延迟抖动。 - 危险:OOM Killer被触发,
dmesg中出现Out of memory日志。
容器场景特别提示: 在容器宿主机上,即使单个容器的内存使用量未达到cgroup限制,全局内存压力仍可能导致该容器触发 Direct Reclaim。这是因为回收是全局行为,不受cgroup内存限制的完全隔离。使用
memory.kmem.tcp.max_usage_in_bytes 等cgroup指标辅助诊断。