← Linux: память
12 мин · средне · Урок 3 из 4

Swap и OOM killer

Как работает подкачка, что настраивает swappiness и по каким правилам OOM killer выбирает жертву.

Как ядро переживает нехватку памяти

Когда свободная память падает ниже порога (watermark), ядро начинает reclaim: освобождает страницы, которые можно вернуть. Фоном этим занимается демон kswapd, его видно в ps.

Первые кандидаты на вылет — страницы page cache: у них есть копия на диске. Анонимные страницы так выбросить нельзя, для них есть swap: ядро пишет неактивную страницу на диск и освобождает физическую память.

$ swapon --show
NAME      TYPE SIZE USED PRIO
/swapfile file   2G 340M   -2

Swap не «медленная память для программ», а способ убрать из RAM то, к чему давно не обращались: heap задремавшего процесса, память демона, который просыпается раз в сутки. Активно используемые данные в swap не живут: каждое обращение к выгруженной странице стоит дисковый ввод-вывод.

swappiness

Насколько охотно ядро предпочитает swap выбрасыванию кэша, настраивает vm.swappiness. Диапазон от 0 до 200: верхнюю границу подняли со ста, когда в ядро завезли новый механизм реклейма. Меньше значение, реже ядро трогает swap.

$ sysctl vm.swappiness
vm.swappiness = 60

60 — типичный дефолт. Для баз данных его часто снижают до 1–10: базе важнее держать свои данные в RAM, чем ядру сохранять page cache. Ставить 0 без понимания не стоит: это агрессивно отключает выгрузку и приближает OOM.

OOM killer

Если память кончилась совсем, реклейм не помог и выделять нечего, ядро запускает OOM killer (out of memory). Он принудительно убивает один процесс, чтобы освободить память и спасти остальную систему.

память на нуле пробит watermark kswapd ищет, что освободить выбросить кэш копия есть на диске свопить анонимные решает swappiness OOM killer убивает по badness score освобождать больше нечего
OOM killer не первый шаг, а последний: до него ядро дважды пытается освободить память само.

Жертва выбирается по badness score: грубо, чем больше памяти процесс занимает, тем выше балл. Балл виден в /proc/<PID>/oom_score, а сдвинуть его можно через /proc/<PID>/oom_score_adj, значения от -1000 до +1000:

$ cat /proc/812/oom_score_adj
0
# запретить убивать этот процесс:
$ echo -1000 > /proc/812/oom_score_adj

-1000 фактически исключает процесс из кандидатов. Так защищают критичные службы; например, sshd на многих дистрибутивах идёт с отрицательным adj.

След OOM killer ищи в логе ядра:

$ dmesg | grep -i "out of memory"
[8821.320993] Out of memory: Killed process 4172 (java)
  total-vm:6291456kB, anon-rss:3145728kB

У dmesg буфер кольцевой, старые строки из него вытесняются. Тот же лог ядра, но с историей за прошлые дни и загрузки, лежит в journald:

$ journalctl -k -g "Out of memory"          # искать по всему логу ядра
$ journalctl -k -b -1 -g "Out of memory"    # в предыдущей загрузке

Классика на проде: java-процесс без лимитов съел всю память, OOM killer убил его ночью, а утром никто не понимает, куда делся сервис. Эти две команды отвечают на вопрос за десять секунд.

Есть и второй убийца. На Ubuntu 22.04 и новее работает systemd-oomd — служба в пространстве пользователя, которая смотрит на давление памяти (PSI) и убивает cgroup целиком, не дожидаясь, пока в дело вступит ядро. Её работа в логе ядра не появится, искать надо отдельно:

$ journalctl -u systemd-oomd

Что важно запомнить

При нехватке памяти ядро сначала освобождает page cache, а анонимные страницы уходят в swap.

Swap хранит неактивные страницы. Активная работа из swap означает, что памяти реально не хватает.

vm.swappiness (0–200) задаёт склонность ядра к выгрузке; дефолт 60.

OOM killer убивает процесс с наибольшим badness score. oom_score_adj от -1000 до +1000 сдвигает приоритет, -1000 исключает процесс.

Убитый ночью сервис ищи через dmesg | grep -i "out of memory" или journalctl -k -g "Out of memory". Если жертву убил не ядерный OOM killer, смотри journalctl -u systemd-oomd.

Прошли материал до конца? Отметьте урок пройденным.