Найти, кого и когда убил OOM
Время из журнала сверь с журналом службы (journalctl -u <служба>) —
станет видно, что служба делала перед гибелью.
Механизм ядра: когда память кончилась на машине или в cgroup с лимитом, завершает процесс сигналом SIGKILL.
sudo journalctl -k -o cat | grep 'Killed process'
кого убили
systemctl show app -p Result -p NRestarts
Result=oom-kill
systemctl show app -p MemoryMax -p MemoryPeak
лимит и пик
cat /sys/fs/cgroup/system.slice/app.service/memory.events
choom -p 1234
оценка процесса и поправка
sudo choom -n -500 -p 1234
беречь процесс от OOM
free -h
available — запас памяти машины
OOM killer (out of memory) — часть ядра, которая
освобождает память, когда её больше неоткуда взять. Сначала ядро
вытесняет кэш страниц и, если есть swap, редко нужные страницы программ.
Когда освобождать нечего, OOM killer выбирает один процесс и завершает его
сигналом SIGKILL. Этот сигнал нельзя перехватить:
программа не успевает ничего записать в свой журнал.
Память кончается в двух местах:
| Где | Когда | Строка в журнале ядра | Среди кого выбирают |
|---|---|---|---|
| вся машина | кончились память и swap системы | Out of memory: Killed process |
все процессы машины |
| cgroup | группа упёрлась в свой лимит | Memory cgroup out of memory: Killed process |
процессы этой группы |
Лимит группы — MemoryMax= в юните systemd или --memory у контейнера
(см. cgroups-namespaces). Он защищает остальную машину: служба с
MemoryMax=64M упирается в свои 64 МиБ, соседи работают дальше. Поэтому
OOM в cgroup случается при свободной памяти в free -h.
На всей машине отчёт другой: в строке oom-kill: стоят
constraint=CONSTRAINT_NONE и global_oom, а итог —
Out of memory: Killed process 1500 (python3) … oom_score_adj:1000. Так
закончился python3, который занял 400 МиБ на машине без swap.
Каждому процессу ядро считает оценку oom_score: чем больше памяти он
держит (RSS, swap, таблицы страниц), тем она выше. Поправка oom_score_adj
от -1000 до 1000 сдвигает оценку: -1000 — не трогать никогда, 1000 —
убивать первым. Завершают процесс с наибольшей оценкой.
Поправку службе ставит systemd из OOMScoreAdjust= юнита: у
systemd-journald она -250. stress-ng ставит своим рабочим процессам
1000, чтобы под OOM попадали они.
Отчёт OOM в журнале ядра — несколько десятков строк: стек вызовов, память
группы, список задач. Главные строки находит grep:
Служба hog с MemoryMax=64M, в которой tail копит ввод в памяти, дала
такие строки:
| Строка | Что из неё взять |
|---|---|
tail invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0 |
кто просил память в момент нехватки; жертвой может стать другой процесс |
memory: usage 65536kB, limit 65536kB, failcnt 597 |
группа упёрлась в свой лимит 64 МиБ |
oom-kill:constraint=CONSTRAINT_MEMCG,…,oom_memcg=/system.slice/hog.service,…,task=tail,pid=1216,uid=0 |
CONSTRAINT_MEMCG — нехватка в cgroup; oom_memcg — какая группа: юнит или контейнер |
Memory cgroup out of memory: Killed process 1216 (tail) total-vm:70808kB, anon-rss:64768kB, … |
жертва — PID и имя; anon-rss — память программы, которую она держала |
Перед последней строкой ядро печатает задачи группы с их памятью в страницах по 4 КиБ (средние столбцы здесь пропущены):
Журнал ядра читают root и группы adm и systemd-journal. Без sudo
journalctl -k печатает -- No entries --, а dmesg — Operation not
permitted.
systemd узнаёт об OOM в группе службы и останавливает службу целиком
(OOMPolicy=stop по умолчанию): остальные её процессы получают SIGTERM.
Итог службы — oom-kill:
С Restart=on-failure служба поднимается снова, упирается в тот же лимит,
и NRestarts растёт. Счётчики группы — в файле memory.events. Служба
vmhog — stress-ng под MemoryMax=96M с OOMPolicy=continue: она
переживает OOM, а stress-ng поднимает рабочий процесс заново.
max — сколько раз группа упиралась в MemoryMax, oom_kill — сколько
процессов в ней завершил OOM killer. Файл живёт, пока служба запущена.
Процесс, убитый SIGKILL, завершается с кодом 137 = 128 + 9 (см.
exit-codes). Оболочка, чей потомок погиб, пишет Killed.
Контейнер Docker после OOM — Exited (137) и OOMKilled: true (см.
docker).
Время из журнала сверь с журналом службы (journalctl -u <служба>) —
станет видно, что служба делала перед гибелью.
Лимит ставят в drop-in — файле дополнения рядом с юнитом (см. drop-in):
MemoryHigh= — мягкий порог: выше него ядро отбирает у группы память и
тормозит её, но не убивает. stress-ng с 100 МиБ под MemoryHigh=64M
отработал без OOM, но вместо 8 секунд шёл 13. Сколько служба занимает
сейчас и сколько занимала больше всего — MemoryCurrent и MemoryPeak в
systemctl show.
Отрицательную поправку ставит только root. Поправка сдвигает очередь, но память не добавляет: если важный процесс сам съел всё, OOM заберёт соседей, а потом и его.
tail ждёт перевода строки и копит весь ввод в памяти, пока не упрётся в
64 МиБ своей группы. Машина при этом не страдает. Нагрузку памятью с
таймером даёт и stress-ng: --vm 1 --vm-bytes 160M --timeout 10s.
systemctl status — activating (auto-restart) (Result: oom-kill),
NRestarts растёт. Служба упирается в MemoryMax при каждом запуске:
лимит меньше, чем ей нужно, или память течёт. Сравни MemoryPeak с
MemoryMax, подними лимит drop-in'ом или найди утечку в приложении.
SIGKILL не даёт программе ничего записать. Ищи в журнале ядра:
sudo journalctl -k | grep -i 'killed process'. В журнале службы
останется Killed от оболочки или код 137.
Главный процесс-оболочка успел выйти сам — с кодом 137, когда OOM убил его потомка, — раньше, чем systemd получил событие OOM:
Это тот же OOM. Подтверждение — строка Memory cgroup out of memory с этим
временем в journalctl -k.
free -h показывает память всей машины, а OOM случился в cgroup. Лимит
группы — systemctl show <служба> -p MemoryMax или файл memory.max в
/sys/fs/cgroup/…; у контейнера — docker inspect.
Процесс из строки invoked oom-killer только попросил память в момент
нехватки. Жертву выбирают по oom_score — обычно самый большой процесс
группы или машины. Сравни anon-rss убитого с остальными в таблице
Tasks state.
С oom_score_adj=-1000 процесс не трогают никогда. Если течёт именно он,
OOM будет убивать всё вокруг — вплоть до sshd и журнала. Береги -1000 для
процессов, чья память не растёт.
free, available, кэш страниц и swap.-k и журнал службы -u.OOMKilled.SIGTERM и SIGKILL.Есть неточность? Сообщить об ошибке в статье