4. 内存回收机制:kswapd内核线程、直接回收、LRU链表、活跃页与非活跃页、回收优先级

当系统内存不足时,Linux内核必须回收部分页面以释放空间。这一过程并非单一机制,而是由kswapd内核线程、直接回收、LRU链表、活跃页与非活跃页以及回收优先级共同构成的协同体系。理解这些组件如何配合,是调优内存压力的基础。

4.1 内存回收的触发路径

内存回收的触发分为两种场景:异步回收(kswapd)和同步回收(直接回收)。两者的核心区别在于调用者是否阻塞等待。

特性 kswapd 异步回收 直接回收
触发条件 空闲内存低于 pages_low 阈值 内存分配请求无法立即满足
执行上下文 内核线程(per-NUMA-node) 调用进程的上下文
阻塞行为 非阻塞,后台运行 阻塞调用进程,直到回收完成
性能影响 低,平滑释放内存 高,可能导致进程延迟抖动
典型场景 内存缓慢消耗 突发内存压力或kswapd来不及回收
关键阈值关系: pages_min < pages_low < pages_high。当空闲内存低于 pages_low 时,kswapd 被唤醒;低于 pages_min 时,直接回收被触发。

4.2 kswapd 内核线程

kswapd 是每个内存节点(NUMA node)上的内核线程,名称格式为 kswapdN(N为节点编号)。它的核心职责是预判内存压力,在系统陷入直接回收之前主动释放页面。

  • 唤醒机制: 由 zone_reclaim 或 balance_pgdat 路径触发,扫描内存区域(zone)的 LRU 链表。
  • 扫描目标: 从 pages_low 开始回收,直到空闲内存恢复到 pages_high 以上。
  • 扫描方向: 优先扫描 inactive 链表,再扫描 active 链表。
  • 控制参数: vm.swappiness 影响 kswapd 对匿名页与文件页的回收倾向。
调优建议: 如果 /proc/vmstat 中 pgscan_direct 持续增长,说明 kswapd 回收能力不足,应考虑增大 vm.min_free_kbytes 或降低 vm.watermark_scale_factor。

4.3 LRU 链表:内存页的组织核心

Linux 使用 LRU(Least Recently Used)链表 来管理所有可回收的内存页。每个内存区域(zone)维护两组 LRU 链表:活跃(active) 和 非活跃(inactive),每组又分为 匿名页(anon) 和 文件页(file)。

# 查看系统 LRU 链表状态(单位:页)
$ cat /proc/meminfo | grep -E "Active|Inactive"
Active(anon):    123456 kB
Inactive(anon):   45678 kB
Active(file):    234567 kB
Inactive(file):  345678 kB
LRU 链表结构: 每个 zone 包含 5 个 LRU 链表:LRU_INACTIVE_ANON、LRU_ACTIVE_ANON、LRU_INACTIVE_FILE、LRU_ACTIVE_FILE、LRU_UNEVICTABLE(不可回收页)。

4.4 活跃页与非活跃页的晋升与降级

页面在活跃与非活跃链表之间移动,形成老化(aging)过程。内核通过 page_referenced() 和 mark_page_accessed() 跟踪访问位。

  1. 新页面加入: 新分配的页面默认放入 inactive 链表尾部。
  2. 晋升(Promotion): 页面在 inactive 链表期间被访问,则移动到 active 链表尾部。
  3. 降级(Demotion): 页面在 active 链表尾部停留足够久且未被访问,则移回 inactive 链表头部。
  4. 回收: 回收时从 inactive 链表头部开始扫描,优先回收最久未使用的页面。
注意: 如果 active 链表过大而 inactive 链表过小,会导致 kswapd 频繁扫描 active 链表,增加 CPU 开销。可通过 /proc/sys/vm/vfs_cache_pressure 调整 dentry/inode 缓存的回收倾向。

4.5 回收优先级与扫描深度

内核使用 扫描优先级(scan priority) 控制每次回收操作的激进程度。优先级范围从 DEF_PRIORITY(默认 12)到 0(最高优先级)。

优先级值 含义 扫描行为
12(默认) 最低优先级,kswapd 初始扫描 仅扫描 inactive 链表,回收少量页面
6 中等优先级 开始扫描 active 链表,尝试降级页面
0 最高优先级,直接回收 全量扫描所有 LRU 链表,强制回收

每次扫描后,如果回收量不足,优先级会递增(数值减小),直到满足回收目标或达到最高优先级。这种机制保证了在轻度压力下开销最小,在重度压力下全力回收。

4.6 内存回收流程总览

内存回收流程总览 内存分配请求 空闲 > pages_low? 正常分配 kswapd 异步回收 扫描 inactive 链表 降级 active → inactive 回收 inactive 页面 直接回收(阻塞) 全量扫描 LRU 强制回收(优先级 0) 空闲内存恢复,分配继续 优先级递增 默认 12 → 0 每次扫描不足 优先级 +1

4.7 关键参数与监控指标

参数/指标 路径/来源 说明
vm.swappiness /proc/sys/vm/swappiness 控制 kswapd 回收匿名页的倾向,范围 0-200,默认 60
vm.min_free_kbytes /proc/sys/vm/min_free_kbytes 设置 pages_min 阈值,影响直接回收触发时机
vm.watermark_scale_factor /proc/sys/vm/watermark_scale_factor 控制 pages_low 和 pages_high 与 pages_min 的间距
pgscan_kswapd /proc/vmstat kswapd 扫描的页面总数,持续增长说明内存压力大
pgscan_direct /proc/vmstat 直接回收扫描的页面总数,非零值表示内存压力已到临界
allocstall /proc/vmstat 直接回收导致的分配延迟次数,应尽量保持为 0
快速诊断命令: grep -E "pgscan|pgsteal|allocstall" /proc/vmstat 可以快速判断系统是否正在经历直接回收压力。

4.8 回收优先级与 OOM 的关系

当回收优先级达到 0 且仍然无法满足分配请求时,内核会触发 OOM Killer。OOM Killer 的选择逻辑基于 oom_score,该值由进程的内存占用、CPU 时间、运行时长等因素综合计算。

# 查看进程的 OOM 评分
$ cat /proc/[pid]/oom_score
# 查看 OOM 评分调整值(可手动调整)
$ cat /proc/[pid]/oom_score_adj
生产环境建议: 监控 allocstall 和 pgscan_direct,如果这两个指标持续增长,说明系统内存配置不足或存在内存泄漏,应优先调整 vm.min_free_kbytes 或增加物理内存,而不是依赖 OOM Killer 兜底。

通过理解 kswapd、直接回收、LRU 链表、活跃/非活跃页以及回收优先级的协同工作方式,你可以更精准地配置 sysctl 参数,在内存压力下保持系统性能稳定。