Короткий ответ
OOM Killer — ядро (или systemd-oomd) уже убило процесс. Сначала текст: dmesg -T | grep -iE 'oom|killed process' и journalctl -k. Там есть PID, имя, anon-rss, иногда cgroup. Свяжите с PSI /proc/pressure/memory и с oom_score живых процессов. Не лечите первым делом swap+10G и не sysctl vm.overcommit_memory=2 без модели. Ubuntu 22.04/24.04 может убить через oomd раньше классического OOM — смотрите journalctl -u systemd-oomd.
Хост host.example (10.0.20.10), пользователь admin. Предшественник давления — RAM и swap.
Симптомы и как отличить
- процесс исчез,
systemctlпоказывает resultoom-kill/signal=KILL; - в dmesg
Out of memory: Killed process 9987 (java); - hang без kill — зависание;
- CPU 100% без роста RSS — CPU.
| Источник | Журнал |
|---|---|
| kernel OOM | journalctl -k, dmesg |
| systemd-oomd | journalctl -u systemd-oomd |
| cgroup MemoryMax | MemoryMax= hit, не обязательно kernel OOM |
Возможные причины
- Утечка heap / слишком большой
-Xmx. pm.max_childrenPHP.- Fork bomb, CI runner.
- Page cache + anon на узком узле, oom_score_adj у важного процесса 0, у жертвы высокий.
- MemoryMax cgroup меньше, чем думает приложение.
- Гость без balloon, хост отнял память.
- Файл tmpfs
/dev/shm.
Диагностика
Сразу после события (пока буфер ядра не уехал):
dmesg -T | grep -iE 'oom|killed process|oom-kill' | tail -n 40
journalctl -k -b --no-pager | grep -iE 'oom|killed process' | tail
journalctl -u systemd-oomd --since 'today' --no-pager
systemctl status systemd-oomd --no-pagerРазбор типичной строки ядра: имя, pid, total-vm, anon-rss, file-rss. Жертва не всегда виновник утечки: OOM выбирает по oom_score (часто крупный java, хотя давил кэш-воркер).
Сейчас:
free -h
cat /proc/pressure/memory
swapon --show
ps -eo pid,user,oom_score,oom_score_adj,rss,cmd --sort=-oom_score | head -n 20
systemd-cgtop -m -n 1Для unit:
systemctl show app.service -p MemoryCurrent,MemoryPeak,MemoryMax,OOMPolicy,OOMScoreAdjustMemoryPeak (systemd 255 на 24.04) полезен; на 22.04 может не быть Peak — смотрите cgroup memory.peak если есть:
cat /sys/fs/cgroup/system.slice/app.service/memory.peak 2>/dev/null
cat /sys/fs/cgroup/system.slice/app.service/memory.current 2>/dev/nullПредыдущий boot, если уже reboot:
journalctl -k -b -1 | grep -i oomPersistent journal обязателен, иначе journald volatile сотрёт улики.
Решение
Сценарий A. Понятная жертва = потребитель
Снизьте workers/heap, поставьте MemoryMax= чуть выше рабочего RSS, чтобы следующий раз убить этот cgroup, не хост:
[Service]
MemoryMax=2G
OOMPolicy=stopDrop-in + daemon-reload. Это не лечение утечки, а радиус поражения.
Сценарий B. oomd убил user.slice
Тяжёлые job не под admin SSH-сессией. systemd-run --scope -p MemoryMax=… или отдельный service.
journalctl -u systemd-oomd -n 30Не systemctl stop systemd-oomd навсегда. Можно исключить ssh через unit, но лучше лимиты job.
Сценарий C. Нужен краткосрочный запас
RAM на гипервизоре или аккуратный swap — см. статью RAM. Swap на медленном диске превратит OOM в hang.
Сценарий D. Утечка подтверждена ростом RSS
Рестарт по таймеру — костыль. Фикс в коде/версии. Сохраните coredumpctl если ещё есть.
Сценарий E. Корневой том и загрузка
OOM в init может оставить машину в emergency — загрузка.
Как связать одну строку dmesg с cgroup и PSI
Разберите поля oom-kill:constraint= (на новых ядрах 24.04): CONSTRAINT_NONE vs CONSTRAINT_MEMCG. Если MEMCG — сработал MemoryMax unit, хост мог быть ещё жив. Тогда виновник — этот cgroup, не «надо RAM всему серверу».
Сверьте timestamp dmesg (-T) с journalctl -u app.service вокруг той же секунды: рестарт unit после KILL. systemctl show -p Result,ExecMainCode даёт oom-kill / signal.
PSI full avg10 > 0 в момент перед kill — подтверждение давления, не «случайный kill». Если PSI ноль, а kill есть — скорее cgroup limit или oomd по другому критерию.
Сохраните:
sudo cp /var/log/kern.log /home/admin/kern-oom.copy
# или
journalctl -k --since '1 hour ago' > /home/admin/kmsg-oom.txtдо reboot. На volatile journal reboot = потеря.
Не повышайте vm.overcommit_memory чтобы «java форкнулась». Это меняет учёт памяти и может отложить OOM, сделав его внезапнее.
Список живых процессов с высоким oom_score до события помогает понять, кто будет следующей жертвой. После kill PID нет в ps, остаётся только dmesg. Поэтому мониторинг oom_score топа — профилактика, не разбор.
MemoryAccounting=yes должен быть включён, иначе MemoryCurrent пуст. На Ubuntu для system.slice обычно да. Для пользовательских сессий — user-1000.slice.
Если убили kthreadd или systemd — это уже не «настроить oom_score_adj у java», это полный memory collapse. Восстановление — reboot и добавление RAM/лимитов до повторения нагрузки.
Связка с диском: если / 100% и journal не пишется, dmesg кольцевой буфер может быть единственным местом с Killed process. Снимите dmesg -T до чистки диска и reboot. Иначе останетесь с симптомом «процесс исчез» без доказательства OOM vs systemctl stop.
oom_kill_allocating_task sysctl меняет жертву на аллоцирующий процесс. Не включайте в инцидент: получите убитый sshd вместо java, если аллоцировал sshd. Сначала cgroup MemoryMax на виновника.
Как проверить, что проблема устранена
Воспроизведите нагрузку (нагрузочный тест, не prod-бомба):
free -h
cat /proc/pressure/memory
journalctl -k --since '10 min ago' | grep -i oom || echo 'no oom'
systemctl show app.service -p MemoryCurrent,MemoryPeakНет новых Killed process. PSI full близок к 0. Сервис жив через час пика.
С ssh admin@10.0.20.10 проверка, что sshd не стал жертвой.
Если не помогло
- OOM каждые N минут: утечка линейная, график RSS.
- Убивают мелкий процесс: смотрите, кто съел остальное (файл cache не в ps rss).
- Hypervisor balloon: свободная память в госте «есть», хост врёт.
Профилактика
- Алерт PSI memory full и MemoryCurrent vs Max.
OOMScoreAdjustтолько точечно для SSH/init.- Persistent journal + размер лимит.
- Не запускать браузер/CI на 2 ГБ прокси.
FAQ
Чем oomd отличается от kernel OOM?
oomd смотрит PSI и может убить раньше, выборочно cgroup. Kernel OOM — последняя линия, часто больнее.
vm.min_free_kbytes крутить?
Не как hotfix без понимания. Слишком большое значение само давит.
Почему убили postgres, а не java?
oom_score, adj, cgroup. postgres мог быть крупнейшим anon в тот момент.
echo f > sysrq-trigger?
Осторожно: принудительный oom. Только консоль и понимание.
Нужно ли Overcommit=2?
Режим «не обещать больше RAM+swap». Меняет fork-поведение. Не включайте в инцидент без расчёта.