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). Он принудительно убивает один процесс, чтобы освободить память и спасти остальную систему.
Жертва выбирается по 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.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс