NAME
kswapd0 — Kernel thread that reclaims memory in the background when a zone's free pages fall below the low watermark; one per NUMA node, kswapd0 is node 0.
kind: kernel thread · binary: kernel thread (PPID 2, no executable) · updated 2026-10-20
SAFE TO KILL?
SYMPTOMS
· kswapd0 pinned at 50–100% CPU while 'available' memory in free -h is close to zero
· si/so columns in vmstat are non-zero: pages are being swapped in and out (swap thrash)
· high iowait, everything feels sluggish, the real hog is a process with a huge RSS
· /proc/pressure/memory shows 'some' avg10 well above 0 — tasks are stalling on memory
DIAGNOSE
FIX
1. Fix the memory hog, not the messenger. A database buffer pool sized for a bigger box, a JVM heap, a leaking worker: shrink it, restart it, or move it. kswapd0 goes quiet by itself when free memory climbs back over the high watermark.
$ ps -eo pid,user,rss,pmem,args --sort=-rss | head -52. Give the kernel somewhere to put cold pages: add a swap file. Persist with a line in /etc/fstab: /swapfile none swap sw 0 0. Swap is not a RAM substitute; it lets reclaim evict genuinely idle anonymous memory instead of thrashing the page cache. On a copy-on-write filesystem such as Btrfs the file must be created with chattr +C first; see the distribution's swap page.
$ sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile3. Wake kswapd earlier so it reclaims in the background instead of stalling allocations. Units are fractions of 10,000 of memory: default 10 (0.1%), maximum 3000. Raise it when allocstall_* counters grow or kswapd_low_wmark_hit_quickly is high. Persist in /etc/sysctl.d/90-reclaim.conf.
$ sudo sysctl vm.watermark_scale_factor=2004. Tell reclaim what to evict first with swappiness. Default 60, range 0–200. Lower keeps anonymous memory and drops page cache first (databases that manage their own cache); 100 treats both as equal cost; values above 100 are legitimate with zram/zswap, the kernel docs give 133 as an example for swap twice as fast as the filesystem.
$ sudo sysctl vm.swappiness=105. Let a userspace OOM daemon end the thrash before the kernel OOM killer does. systemd-oomd (Ubuntu 22.04+ ships it, enabled on Desktop, usually inactive on Server) and earlyoom watch PSI and kill the hog at a threshold you choose, instead of letting kswapd0 spin for ten minutes first.
$ systemctl is-active systemd-oomd6. On kernels with Multi-Gen LRU, protect the working set. 0x0007 means MGLRU is on, 0x0000 means compiled in but switched off (write y to enable), 'No such file' means the kernel lacks CONFIG_LRU_GEN (kernel ≥ 6.1; WSL2 lacks it, a 6.18 cloud guest shipped it off). min_ttl_ms keeps the last N ms of working set in memory and prefers an OOM kill over thrashing; 1000 is the documented starting point.
$ cat /sys/kernel/mm/lru_gen/enabledIMPOSTORS
DISTRIBUTIONS
ubuntu 22.04+ ships systemd-oomd (active on Desktop, inactive on Server unless enabled); PSI is on by default; MGLRU is enabled on the generic 6.x kernels but not on every cloud or WSL2 kernel — check /sys/kernel/mm/lru_gen/enabled.
any Cloud images often ship with zero swap. A 1–2 GiB swap file plus vm.swappiness=10 is the usual first fix on a small VPS.
APPLIES TO
Linux 2.6 and later. /proc/pressure needs kernel 4.20+ with CONFIG_PSI; Multi-Gen LRU needs 6.1+ with CONFIG_LRU_GEN. Per-cgroup pressure is in memory.pressure on cgroup v2.
SEE ALSO
kcompactd(7) — card not written yet, systemd-oomd(7) — card not written yet, kworker(7) — card not written yet
SOURCES
1. Kernel docs — Concepts overview: reclaim, watermarks, direct reclaim, fetched 2026-10-20
2. Kernel docs — sysctl/vm: swappiness, watermark_scale_factor, min_free_kbytes, fetched 2026-10-20
3. Kernel docs — PSI: pressure stall information, fetched 2026-10-20
4. Kernel docs — Multi-Gen LRU admin guide, fetched 2026-10-20
Machine-readable: /process/kswapd0.json · changelog 2026-10-20: first version. Every command was run on both test kernels; both run the distro in a PID namespace that hides kernel threads, so the ps -C kswapd0 / readlink impostor checks ran against an empty result and still want a pass on a bare-metal or full-VM kernel. The 6.18 guest had no /proc/pressure (built without CONFIG_PSI) and MGLRU compiled in but off (0x0000) — both now noted in the card