Linux 内存回收机制:水位线、kswapd 与 Direct Reclaim
源码基线:Linux v6.18.9 (
mm/page_alloc.c/mm/vmscan.c)。现象 :内存明明还有(
free数 GB),进程却偶发卡顿几百毫秒,P99 翻倍。根因 :kswapd 回收跟不上分配速度,水位跌破
min后,业务线程被迫在分配路径上亲自回收(Direct Reclaim),并触发内核节流。
一、三条水位线:谁来收债
text
─────────────────── high ← kswapd 回收到这里就睡
─────────────────── low ← 跌破这里唤醒 kswapd(后台收债,业务无感)
─────────────────── min ← 跌破这里,分配线程自己收债(Direct Reclaim,卡顿)
| 水位区间 | 行为 | 业务感知 |
|---|---|---|
| 高于 low | 快路径直接返回页框 | 无 |
| low ~ min | 唤醒 kswapd 后台回收 | 基本无 |
| 低于 min | 当前线程进入慢路径亲自回收 | 卡顿 |
bash
grep -A5 "Node 0" /proc/zoneinfo | grep -E "min|low|high" # 查看水位(单位:页)
二、Direct Reclaim 调用链(v6.18.9)
text
alloc_pages()
└─ 快路径失败 → __alloc_pages_slowpath() # 先 wake_all_kswapds(),
# 再按 ALLOC_WMARK_MIN 重试
└─ __alloc_pages_direct_reclaim() # psi_memstall_enter()
└─ __perform_reclaim()
└─ try_to_free_pages() # ← 业务线程开始收债
└─ do_try_to_free_pages() → shrink_zones() → shrink_node()
(MGLRU 启用时走 lru_gen_shrink_node())
└─ 收到页返回(几十~几百 ms);收不到 → OOM
7.0 中
shrink_zones()更名为shrink_one(),其余不变。
为什么慢------回收线程扫到不同页的代价:
| 页类型 | 代价 | 量级 |
|---|---|---|
| 干净 Page Cache | 直接丢 | 微秒 |
| 脏页 | 回写 I/O + 节流 | 毫秒起 |
| 匿名页 | 换出 swap/zram | 毫秒 |
三级节流(卡顿的真正来源):
- 撞见大量回写页 →
reclaim_throttle(VMSCAN_THROTTLE_WRITEBACK),睡最长 100ms - 回收连续无进展 →
VMSCAN_THROTTLE_NOPROGRESS - min 以下保护区耗尽 →
throttle_direct_reclaim(),睡等 kswapd 救回水位
多线程同时分配会集体进 Direct Reclaim,形成"集体抖动"。
MGLRU(6.12 起默认)改变扫描方式,但水位线、触发条件、计数器全部不变,排查方法照用。
三、观测与实锤
bash
grep -E "allocstall|pgscan_direct|pgscan_kswapd|pgsteal|throttle" /proc/vmstat
| 计数器 | 含义 | 信号 |
|---|---|---|
pgscan_kswapd |
后台扫描量 | 稳定 = 正常 |
pgscan_direct |
前台扫描量 | 持续增长 = 业务在收债 |
allocstall_* |
进 Direct Reclaim 次数 | 增长 = 卡顿元凶 |
pgscan_direct_throttle |
睡等 kswapd 救援次数 | 非零 = 最危险阶段 |
bash
drsnoop-bpfcc # 专抓 Direct Reclaim 延迟
cat /proc/pressure/memory # full avg10 高 = 撞见回写
perf stat -e 'vmscan:mm_vmscan_direct_reclaim_begin' -a sleep 10
典型卡顿栈:__alloc_pages_direct_reclaim → __perform_reclaim → try_to_free_pages → shrink_node → reclaim_throttle
四、缓解手段
| 手段 | 作用点 | 代价 |
|---|---|---|
调大 vm.min_free_kbytes |
抬水位,给 kswapd 提前量 | 常驻内存变少 |
调大 vm.watermark_scale_factor |
更早唤醒 kswapd | 同上 |
降低 vm.swappiness |
倾向回收文件页 | 匿名页压力大时 OOM 更早 |
| 业务削峰 / 内存池 | 避免集体进慢路径 | 改业务 |
| cgroup 限额 | 压力隔离到容器 | 限额内仍会发生 |
查磁盘与 vm.dirty_* |
降低回写节流命中 | 基础设施成本 |
排查顺序 :pgscan_direct 涨 → 确认 Direct Reclaim → pgscan_direct_throttle 涨 → 抬水位+查 kswapd → 否则查 Dirty/Writeback 与磁盘 → 再查 swap/zram。
能做 :隔一分钟跑一次 grep -E "allocstall|pgscan_direct" /proc/vmstat,数字在涨,说明你的业务线程正在替内核收债。