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_INACTIVE_ANON、LRU_ACTIVE_ANON、LRU_INACTIVE_FILE、LRU_ACTIVE_FILE、LRU_UNEVICTABLE(不可回收页)。
4.4 活跃页与非活跃页的晋升与降级
页面在活跃与非活跃链表之间移动,形成老化(aging)过程。内核通过 page_referenced() 和 mark_page_accessed() 跟踪访问位。
- 新页面加入: 新分配的页面默认放入 inactive 链表尾部。
- 晋升(Promotion): 页面在 inactive 链表期间被访问,则移动到 active 链表尾部。
- 降级(Demotion): 页面在 active 链表尾部停留足够久且未被访问,则移回 inactive 链表头部。
- 回收: 回收时从 inactive 链表头部开始扫描,优先回收最久未使用的页面。
/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 内存回收流程总览
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 参数,在内存压力下保持系统性能稳定。