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的关键手段。
内存回收双路径流程图 内存分配请求 空闲 > pages_low? 正常分配 否 空闲 > pages_min? kswapd 后台回收 异步、非阻塞 否 Direct Reclaim 同步、阻塞进程 重试内存分配 关键水位线 pages_low: 后台回收阈值 pages_min: 直接回收阈值 pages_high: 停止回收阈值

4.2 kswapd内核线程:后台回收的守护者

kswapd是内核为每个NUMA节点创建的一个内核线程,名称格式为kswapdN(N为节点编号)。它的职责是在内存压力尚未达到临界点之前,主动回收内存页,使空闲内存保持在pages_high水位线以上。

4.2.1 kswapd的工作流程

  1. 唤醒条件:当某个NUMA节点的空闲内存低于pages_low水位线时,内存分配路径会唤醒该节点的kswapd线程。
  2. 扫描与回收:kswapd遍历节点的LRU链表(活跃/不活跃页列表),按照优先级从低到高扫描,回收符合条件的页(如文件页、匿名页)。
  3. 停止条件:当空闲内存恢复到pages_high水位线以上时,kswapd进入睡眠状态,等待下一次唤醒。
  4. 平衡策略: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
kswapd 状态机与水位线关系 空闲 > pages_high kswapd 睡眠 pages_low < 空闲 < pages_high kswapd 正在回收 空闲 < pages_min Direct Reclaim 触发 内存消耗 持续消耗 回收恢复 回收充足 调优目标: 通过调整水位线参数,使系统尽可能停留在绿色区域,避免进入红色区域触发 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)

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 终止进程释放内存 进程被杀死,业务中断
水位线与回收触发关系图 空闲内存量 pages_high pages_low pages_min 绿色区:无回收,快速分配 黄色区:kswapd 后台回收 红色区:Direct Reclaim 触发 调优目标:扩大绿色区与黄色区,避免进入红色区。通过调整 min_free_kbytes 和 watermark_scale_factor 实现。

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指标辅助诊断。