OOM killer
Когда физическая память исчерпана, OOM killer выбирает жертву по oom_score и убивает её. oom_score_adj смещает выбор. MemoryMax в cgroup запускает локальный OOM до исчерпания системной памяти. Убийство видно в dmesg и journalctl.
Сервис исчезает. Нет лога краша. Нет трассировки стека. Мониторинг показывает: был запущен, стал недоступен за секунду. Ищешь в /var/log/syslog — ничего. Проверяешь dmesg и видишь: Out of memory: Killed process 8823 (java) score 742 or sacrifice child. Сработал OOM killer. Он выбрал твою JVM, решил что она лучший кандидат для жертвы, и завершил её без предупреждения — никакого SIGTERM, сразу SIGKILL. Понять почему OOM killer выбрал именно твой процесс, как прочитать событие убийства и как сделать это менее вероятным — навык senior Linux-оператора.
После этого урока ты сможешь найти OOM kill в dmesg и journalctl, читать oom_score чтобы понять почему процесс был выбран, использовать oom_score_adj для смещения выбора, различать системный OOM от OOM cgroup (MemoryMax), и объяснять переопределение памяти (overcommit) и почему оно делает OOM kill возможным.
Почему OOM killer существует: переопределение памяти (overcommit).
Linux выделяет память оптимистично. Когда процесс вызывает malloc() или mmap(), ядро предоставляет виртуальное адресное пространство, но не подкрепляет его физической RAM до момента реальной записи (demand paging). Это overcommit: сумма виртуальной памяти всех процессов превышает физическую RAM плюс своп. Работает потому что большинство выделений никогда полностью не используются.
# Проверить политику overcommit:
cat /proc/sys/vm/overcommit_memory
# 0 = эвристика (по умолчанию): разрешать некоторый overcommit
# 1 = всегда разрешать: никогда не отказывать в malloc
# 2 = никогда не превышать: committed <= RAM + swap * ratio
# Текущее состояние:
grep -E 'MemTotal|MemAvailable|CommitLimit|Committed_AS' /proc/meminfo
# MemTotal: 16384000 kB
# MemAvailable: 2048000 kB ← реально свободно
# CommitLimit: 20971520 kB ← максимум обещаний (RAM + swap)
# Committed_AS: 18500000 kB ← сколько уже обещано процессам
# Когда Committed_AS приближается к CommitLimit и страницы реально трогаются,
# ядро исчерпывает физические страницы. Оно должно либо:
# а) Взять страницы из свопа (если своп есть и не исчерпан)
# б) Вызвать OOM killeroom_score: как ядро выбирает жертву.
У каждого процесса есть oom_score от 0 до 1000. Ядро убивает процесс с наибольшим счётом. Счёт вычисляется из RSS, использования свопа, времени работы и oom_score_adj.
# Прочитать oom_score процесса:
cat /proc/1234/oom_score
# 742 ← высокий: вероятная жертва OOM
# Прочитать oom_score_adj (смещение):
cat /proc/1234/oom_score_adj
# 0 ← смещение не применено (диапазон: -1000 до +1000)
# oom_score_adj = -1000: НИКОГДА не убивать (ядро пропускает)
# oom_score_adj = +1000: убить первым (для пакетных задач)
# Защитить критический процесс (например sshd):
echo -1000 | sudo tee /proc/$(pgrep sshd)/oom_score_adj
# Установить высокий счёт для утилизируемого воркера:
echo 500 | sudo tee /proc/$(pgrep batch-worker)/oom_score_adj
# Постоянно — в unit-файле systemd:
# [Service]
# OOMScoreAdjust=-900 (для критических сервисов вроде БД)
# OOMScoreAdjust=200 (для пакетных задач)
# Сканировать все процессы по oom_score:
for pid in /proc/[0-9]*/oom_score; do
score=$(cat "$pid" 2>/dev/null)
comm=$(cat "${pid%oom_score}comm" 2>/dev/null)
echo "$score $comm"
done | sort -rn | head -10Чтение события OOM kill в dmesg и journalctl.
# Проверить dmesg на OOM-события:
sudo dmesg | grep -A 20 'Out of memory'
# [123456.789] Out of memory: Killed process 8823 (java) total-vm:4194304kB,
# anon-rss:3145728kB, file-rss:65536kB, shmem-rss:0kB,
# UID:1000 pgtables:12288kB oom_score_adj:0
# Расшифровка полей:
# total-vm:4194304kB = 4 ГБ виртуального адресного пространства
# anon-rss:3145728kB = 3 ГБ анонимных страниц (куча/стек) в физической RAM
# file-rss:65536kB = 64 МБ файловых страниц (mmap, разделяемые библиотеки)
# oom_score_adj:0 = смещение не применялось
# В journalctl (journald захватывает сообщения ядра):
sudo journalctl -k | grep -i 'oom\|killed process\|out of memory' | tail -20
# journald также фиксирует unit systemd который был убит:
sudo journalctl -u your-service --since '1 hour ago' | grep -i 'kill\|oom'
# "Memory cgroup out of memory: Killed process ..." (OOM cgroup)
# или строка ядра выше (системный OOM)
# Полный снимок памяти на момент OOM kill:
sudo dmesg | grep -B 5 -A 50 'Out of memory' | head -80
# Ядро выводит таблицу всех процессов с RSS и oom_scoreOOM cgroup против системного OOM: MemoryMax срабатывает первым.
Когда у systemd-сервиса установлен MemoryMax, он получает локальный OOM kill cgroup как только cgroup превышает этот лимит — даже если на хосте гигабайты свободной памяти.
# Различить в логах:
sudo journalctl -k | grep 'oom'
# Системный OOM:
# "Out of memory: Killed process 8823 (java) score 742..."
#
# OOM cgroup (превышение MemoryMax):
# "Memory cgroup out of memory: Killed process 8823 (java)..."
# или (на новых ядрах):
# "oom-kill:constraint=CONSTRAINT_MEMCG,..."
# Проверить установлен ли MemoryMax:
systemctl show your-service | grep -E 'MemoryMax|MemoryHigh|MemoryCurrent'
# MemoryMax=536870912 ← 512 МБ жёсткий потолок
# MemoryCurrent=498073600 ← сейчас 475 МБ — близко к потолку!
# OOM kill cgroup виден в журнале сервиса:
sudo journalctl -u your-service | grep -i kill
# Настройка: если сервису реально нужно больше памяти — поднять MemoryMax:
sudo systemctl set-property your-service.service MemoryMax=1G
# Если у сервиса утечка памяти — MemoryMax страховочная сетка,
# но реальное исправление — устранить утечку.Инцидент: Java-сервис убит ночью — разбор OOM-инцидента.
Алерт срабатывает в 06:14: payment-service недоступен. Ночью деплоя не было.
# Шаг 1: подтвердить что это OOM kill, а не краш или деплой
sudo journalctl -u payment-service --since '2026-06-22 00:00' | tail -30
# Jun 22 06:14:03 prod-1 kernel: Memory cgroup out of memory:
# Killed process 11203 (java) total-vm:8388608kB, anon-rss:1572864kB ...
# Jun 22 06:14:04 prod-1 systemd[1]: payment-service.service: Main process
# exited, code=killed, status=9/KILL
# Jun 22 06:14:04 prod-1 systemd[1]: payment-service.service: Failed with
# result 'oom-kill'.
# ← поле 'oom-kill': systemd знает что это OOM
# Шаг 2: состояние памяти на момент убийства
sudo journalctl -k --since '2026-06-22 06:13' --until '2026-06-22 06:15'
# Дамп ядра показывает RSS всех процессов на момент убийства
# Шаг 3: OOM cgroup или системный OOM?
# 'Memory cgroup out of memory' → OOM cgroup (превышен MemoryMax)
# В данном случае: OOM cgroup при MemoryMax=1.5G
# Шаг 4: 1.5G — правильный лимит для этого сервиса?
systemctl show payment-service | grep Memory
# MemoryMax=1610612736 (1.5 ГБ)
# MemoryCurrent на момент убийства: ~1.58 ГБ (из лога dmesg)
# Шаг 5: расследование роста памяти
# Проверить нагрузку — был ли всплеск трафика?
grep '06:1[0-4]' /var/log/nginx/access.log | wc -l
# 48203 ← против обычных ~5000: всплеск x10 в 06:10
# Шаг 6: немедленное устранение
# Вариант А: поднять MemoryMax (выигрывает время, не исправляет утечку)
sudo systemctl set-property payment-service.service MemoryMax=3G
sudo systemctl restart payment-service
# Вариант Б: добавить MemoryHigh для раннего предупреждения
# [Service]
# MemoryHigh=1200M (мягкое давление: ядро агрессивно рекламирует)
# MemoryMax=1600M (жёсткий потолок: OOM kill при превышении)
# Шаг 7: защитить sshd чтобы следующий OOM не убил доступ к серверу
echo -1000 | sudo tee /proc/$(pgrep -x sshd | head -1)/oom_score_adj
# В unit-файле sshd: OOMScoreAdjust=-1000▸Почему это работает
На macOS нет OOM killer в Linux-смысле. Вместо него macOS использует memory pressure и jetsam (обработчик нехватки памяти, пришедший из iOS). При нехватке памяти jetsam завершает фоновые процессы по таблице приоритетов. В логах macOS никогда не увидишь Out of memory: Killed process — аналог это записи jetsam в /var/log/system.log или Console.app. Модель overcommit тоже другая: macOS использует компрессор, который сжимает страницы памяти перед вытеснением.
▸Частая ошибка
Повышение MemoryMax или настройка oom_score_adj не исправляет утечку памяти — это меняет лишь кто умрёт. Если сервис реально утекает по памяти и ты поднял MemoryMax — он в итоге дорастёт до нового лимита и снова будет убит OOM killer’ом или съест столько RAM что будет убит другой (невиновный) процесс. oom_score_adj=-900 на утекающем процессе просто означает что OOM killer выберет другую жертву. Правильный подход: воспринимать OOM kill как отчёт о симптоме. Использовать heap-профилирование, GC-логи и графики тренда памяти для поиска утечки. MemoryMax и oom_score_adj — операционные ограничители пока устраняется корневая причина, а не постоянное решение.
journalctl показывает: 'Memory cgroup out of memory: Killed process 5501 (node)'. У хоста 4 ГБ свободной RAM. Как это возможно и что вызвало убийство?
OOM killer — последнее средство ядра при исчерпании физической памяти. Он оценивает каждый процесс по RSS, времени работы и oom_score_adj, затем отправляет SIGKILL процессу с наибольшим счётом — без предупреждения, без SIGTERM. OOM kill ищи в dmesg (Out of memory: Killed process) или journalctl -k; systemd записывает поле oom-kill в журнал сервиса. oom_score_adj (диапазон −1000 до +1000) смещает выбор: устанавливай −1000 для sshd и других критических процессов; +200 для утилизируемых пакетных задач. OOM cgroup (Memory cgroup out of memory) срабатывает когда сервис превышает MemoryMax — даже при гигабайтах свободной памяти на хосте. Используй MemoryHigh как точку мягкого давления ниже MemoryMax для раннего предупреждения до жёсткого убийства. Overcommit (/proc/sys/vm/overcommit_memory) — корневая причинная модель: ядро обещает больше памяти чем существует, и OOM killer убирает последствия когда обещание нельзя выполнить.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.