Короткий ответ

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 показывает result oom-kill / signal=KILL;
  • в dmesg Out of memory: Killed process 9987 (java);
  • hang без kill — зависание;
  • CPU 100% без роста RSS — CPU.
ИсточникЖурнал
kernel OOMjournalctl -k, dmesg
systemd-oomdjournalctl -u systemd-oomd
cgroup MemoryMaxMemoryMax= hit, не обязательно kernel OOM

Возможные причины

  1. Утечка heap / слишком большой -Xmx.
  2. pm.max_children PHP.
  3. Fork bomb, CI runner.
  4. Page cache + anon на узком узле, oom_score_adj у важного процесса 0, у жертвы высокий.
  5. MemoryMax cgroup меньше, чем думает приложение.
  6. Гость без balloon, хост отнял память.
  7. Файл 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,OOMScoreAdjust

MemoryPeak (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 oom

Persistent journal обязателен, иначе journald volatile сотрёт улики.

Решение

Сценарий A. Понятная жертва = потребитель

Снизьте workers/heap, поставьте MemoryMax= чуть выше рабочего RSS, чтобы следующий раз убить этот cgroup, не хост:

[Service]
MemoryMax=2G
OOMPolicy=stop

Drop-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-поведение. Не включайте в инцидент без расчёта.