kswapd and the Memory Watermarks: How the Kernel Reclaims Pages Before You Hit OOM

2026-06-27

Every NUMA node in Linux has a per-zone kernel thread named kswapd0, kswapd1, etc. Its job is to reclaim pages before allocations start failing. If allocations ever did fail outright, you'd hit direct reclaim (slow, synchronous, on the allocating thread's back) or worse, the OOM killer. kswapd is the asynchronous buffer between "plenty of free memory" and "everything is on fire."

Each zone tracks three watermarks, computed from min_free_kbytes:

Once awake, kswapd walks the LRU lists (active/inactive, anon/file) and reclaims pages: clean file-backed pages are dropped immediately, dirty pages get written back, anonymous pages get swapped (if swap is configured), and slab caches get shrunk via registered shrinkers. It scans in proportion to swappiness — at 60 (default), file pages are preferred over anon pages, but not exclusively.

Concrete example. A 64 GB server with min_free_kbytes=90112 (≈88 MB) and one NUMA node: min ≈ 88 MB, low ≈ 110 MB, high ≈ 132 MB. You stream a 50 GB file through read(). The page cache grows until free memory drops below 110 MB. kswapd0 wakes up and starts dropping clean cached pages from the inactive file LRU. You see a tiny CPU blip on one core (kswapd0), but read() never blocks. If you'd instead allocated 50 GB of anonymous memory faster than kswapd could swap, you'd cross the min watermark, hit direct reclaim, and your allocating thread would stall doing the same work kswapd was trying to do for you.

Rule of thumb. Watch /proc/vmstat: pgscan_kswapd versus pgscan_direct. If direct scan counts are a meaningful fraction of kswapd scan counts (say, >10%), your workload is allocating faster than kswapd reclaims. Raise min_free_kbytes to widen the gap between low and min — this gives kswapd more runway before threads start reclaiming on their own.

kswapd also triggers kcompactd when allocations need contiguous high-order pages, and it backs off when a zone is unreclaimable to avoid burning CPU on a lost cause — that's the path that eventually ends at the OOM killer.

Key Takeaway: kswapd reclaims memory asynchronously when free pages dip below the low watermark, keeping allocations off the slow direct-reclaim path — and pgscan_direct in /proc/vmstat tells you when it's losing the race.

All newsletters