Linux 内存回收机制:水位线、kswapd 与 Direct Reclaim
源码基线:Linux v6.18.9(
mm/page_alloc.c/mm/vmscan.c)。现象:内存明明还有(
free数 GB),进程却偶发卡顿几百毫秒,P99 翻倍。
根因:kswapd 回收跟不上分配速度,水位跌破min后,业务线程被迫在分配路径上亲自回收(Direct Reclaim),并触发内核节流。
一、三条水位线:谁来收债
─────────────────── high ← kswapd 回收到这里就睡 ─────────────────── low ← 跌破这里唤醒 kswapd(后台收债,业务无感) ─────────────────── min ← 跌破这里,分配线程自己收债(Direct Reclaim,卡顿)| 水位区间 | 行为 | 业务感知 |
|---|---|---|
| 高于 low | 快路径直接返回页框 | 无 |
| low ~ min | 唤醒 kswapd 后台回收 | 基本无 |
| 低于 min | 当前线程进入慢路径亲自回收 | 卡顿 |
grep-A5"Node 0"/proc/zoneinfo|grep-E"min|low|high"# 查看水位(单位:页)二、Direct Reclaim 调用链(v6.18.9)
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);收不到 → OOM7.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 起默认)改变扫描方式,但水位线、触发条件、计数器全部不变,排查方法照用。
三、观测与实锤
grep-E"allocstall|pgscan_direct|pgscan_kswapd|pgsteal|throttle"/proc/vmstat| 计数器 | 含义 | 信号 |
|---|---|---|
pgscan_kswapd | 后台扫描量 | 稳定 = 正常 |
pgscan_direct | 前台扫描量 | 持续增长 = 业务在收债 |
allocstall_* | 进 Direct Reclaim 次数 | 增长 = 卡顿元凶 |
pgscan_direct_throttle | 睡等 kswapd 救援次数 | 非零 = 最危险阶段 |
drsnoop-bpfcc# 专抓 Direct Reclaim 延迟cat/proc/pressure/memory# full avg10 高 = 撞见回写perfstat-e'vmscan:mm_vmscan_direct_reclaim_begin'-asleep10典型卡顿栈:__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,数字在涨,说明你的业务线程正在替内核收债。