Когда серверу не хватает памяти, Linux не останавливается целиком: ядро выбирает процесс и принудительно завершает его. Этот механизм называется OOM killer (от out of memory — «память кончилась»). Снаружи это выглядит как внезапное падение базы данных, ошибка 502 на сайте или контейнер, который перезапускается без видимой причины. Ниже — как убедиться, что дело в памяти, найти, какой процесс завершён и почему, и что сделать, чтобы это не повторялось.
Команды проверены на Ubuntu 24.04 (systemd 255) и Ubuntu 26.04 (systemd 259), примеры с контейнерами — на Docker 29. Все сообщения ядра и systemd в статье — настоящий вывод из наших тестов.
Признаки нехватки памяти на сервере
- Служба внезапно перезапустилась, а в её журнале нет ошибки — только
code=killed, status=9/KILL. - Контейнер Docker остановился с кодом
137. - В журнале PostgreSQL —
client backend (PID 83) was terminated by signal 9: Killed, после чего база обрывает все подключения и перезапускается. - Команда в терминале оборвалась со словом
Killed. - Сервер долго отвечает, вход по SSH занимает десятки секунд, диск постоянно занят.
Сигнал 9 (SIGKILL) и код 137 (128 + 9) сами по себе не доказывают нехватку памяти: так же завершается процесс по kill -9. Доказательство — запись в журнале ядра, о ней ниже.
Как читать вывод free -h
free -h
Вывод на нашей тестовой машине (4 ГБ памяти, 1 ГБ подкачки) в момент, когда памяти не хватало:
total used free shared buff/cache available
Mem: 3.8Gi 2.7Gi 63Mi 87Mi 1.3Gi 1.1Gi
Swap: 1.0Gi 1.0Gi 32Ki
total— вся память,used— занято программами.free— совсем не используется. Маленькое значение здесь — норма: Linux занимает свободную память под кеш файлов.buff/cache— кеш файлов и буферы ядра. Большую часть кеша ядро отдаёт программам по первому требованию.available— главный столбец: сколько памяти могут получить программы, учитывая кеш, который можно освободить.Swap— подкачка: участок диска, куда ядро выносит редко используемую память.
По free 63 МБ выглядит тревожно, но available — 1,1 ГБ, то есть прямо сейчас память есть. Тревожный признак в этом выводе другой — подкачка занята полностью: недавно памяти не хватило, и ядро вынесло на диск всё, что могло.
Если available в часы нагрузки регулярно опускается ниже 10 % от total, памяти мало.
vmstat: поля si и so показывают работу подкачки
free показывает момент, а vmstat — что происходит каждую секунду:
vmstat 1 6
procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
r b swpd free buff cache si so bi bo in cs us sy id wa st gu
0 0 1048400 161836 4260 1270584 6 8 13674 919 4579 10 5 4 91 0 0 0
1 0 1048372 68376 4680 1318176 68 60 99328 60 6749 7141 40 9 50 1 0 0
1 0 1048352 140808 4040 1287604 20 0 45132 240 3529 2531 16 6 78 1 0 0
1 0 1048372 65872 3572 1327492 48 220 157552 7804 9445 9595 19 15 56 10 0 0
2 0 1048364 95968 4608 1319124 4 0 14680 60 3748 2234 16 11 69 4 0 0
7 0 1048352 69220 3312 1232392 12 0 129016 0 8178 9361 20 14 64 2 0 0
Первая строка — средние значения с момента загрузки, смотрите на следующие.
si/so— сколько килобайт в секунду ядро читает из подкачки и пишет в неё. Если оба столбца ненулевые постоянно, память работающих программ уже не помещается в оперативную, и сервер тратит время на перекладывание страниц между памятью и диском.bi— чтение с диска. Большие значения при маленькомcacheзначат, что ядру не хватает памяти даже на кеш файлов и оно перечитывает их с диска.wa— доля времени, когда процессор ждёт диск.b— процессы, ожидающие ввода-вывода.
В выводе выше so доходит до 220 КБ/с, а bi — до 157 МБ/с: машина работает на пределе.
Ещё один показатель — давление на память (PSI), доля времени, в течение которого процессы ждали память:
cat /proc/pressure/memory
some avg10=1.27 avg60=5.41 avg300=21.02 total=5374333492
full avg10=1.22 avg60=4.57 avg300=15.49 total=3809828370
some avg300=21.02 — за последние 5 минут в 21 % времени хотя бы один процесс ждал память. На здоровом сервере здесь нули или доли процента.
Где искать OOM killer: dmesg и journalctl -k
Каждое срабатывание OOM killer ядро записывает в свой журнал. На Ubuntu журнал ядра читается с sudo:
sudo journalctl -k -g "out of memory" --since "2 days ago"
sudo dmesg -T | grep -i -E "out of memory|killed process"
Шаблон в journalctl -g пишите строчными буквами: тогда поиск не учитывает регистр и находит обе формы сообщения — Out of memory и Memory cgroup out of memory. С заглавной буквой вторая форма не найдётся.
Найдутся строки двух видов.
Кончилась память всего сервера. Так ядро завершило MySQL на нашей тестовой машине, когда другие процессы заняли всю память:
containerd invoked oom-killer: gfp_mask=0x140cca(GFP_HIGHUSER_MOVABLE|__GFP_COMP), order=0, oom_score_adj=-500
oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/docker/83e3731abda6…,task=mysqld,pid=56630,uid=999
Out of memory: Killed process 56630 (mysqld) total-vm:2177364kB, anon-rss:332220kB, file-rss:1184kB, shmem-rss:0kB, UID:999 pgtables:1264kB oom_score_adj:0
Процесс упёрся в ограничение своей службы или контейнера (о том, как его задать, — ниже):
python invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
memory: usage 102400kB, limit 102400kB, failcnt 108
oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=b3ef78fd1125…,mems_allowed=0,oom_memcg=/docker/b3ef78fd1125…,task_memcg=/docker/b3ef78fd1125…,task=python,pid=42515,uid=0
Memory cgroup out of memory: Killed process 42515 (python) total-vm:116168kB, anon-rss:101572kB, file-rss:4kB, shmem-rss:0kB, UID:0 pgtables:264kB oom_score_adj:0
Как читать:
X invoked oom-killer— процесс, которому не хватило памяти в этот момент. Это не обязательно виновник: в первом примере память просил containerd, а завершён mysqld.constraint=CONSTRAINT_NONEиglobal_oom— кончилась память всего сервера.CONSTRAINT_MEMCG— кончился лимит группы процессов (контейнера или службы systemd), остальной сервер не пострадал.Killed process 56630 (mysqld)— кого завершили.anon-rss— сколько памяти процесс занимал на момент завершения: у MySQL — 332 МБ.- Перед этими строками ядро печатает таблицу всех процессов с их памятью (
Tasks state) — по ней видно, кто ещё занимал память.
Служба systemd
Если ядро завершило процесс службы, systemd пишет об этом в журнал службы:
systemctl status myapp
journalctl -u myapp -o cat
Active: failed (Result: oom-kill) since Sun 2026-10-11 11:19:28 UTC; 6s ago
myapp.service: A process of this unit has been killed by the OOM killer.
myapp.service: Main process exited, code=killed, status=9/KILL
myapp.service: Failed with result 'oom-kill'.
Так пишет systemd 255 в Ubuntu 24.04. В Ubuntu 26.04 (systemd 259) первая строка звучит иначе: The kernel OOM killer killed some processes in this unit. Ищите по oom-kill — эта строка одинакова в обеих версиях.
Контейнер Docker
Контейнер, процесс которого завершён по нехватке памяти, останавливается с кодом 137, а Docker отмечает это в его состоянии:
docker ps -a # Exited (137)
docker inspect -f '{{.State.OOMKilled}} {{.State.ExitCode}}' myapp # true 137
docker events --filter event=oom # следить за новыми случаями
OOMKilled становится true и при ограничении контейнера, и при нехватке памяти на всём сервере: так было с MySQL в примере выше.
Как OOM killer выбирает процесс: oom_score и oom_score_adj
Ядро считает для каждого процесса «очки» — в основном по тому, сколько памяти он занимает, — и завершает процесс с наибольшими очками. Поэтому первыми под удар попадают базы данных и крупные приложения.
Очки и поправка к ним лежат в /proc:
cat /proc/1234/oom_score # текущие очки, чем больше — тем вероятнее завершение
cat /proc/1234/oom_score_adj # поправка от -1000 до 1000, по умолчанию 0
Список процессов с наибольшими очками:
for p in /proc/[0-9]*; do printf "%s %s %s\n" "$(cat $p/oom_score)" "${p#/proc/}" "$(cat $p/comm)"; done 2>/dev/null | sort -rn | head
Поправку меняют, чтобы защитить важный процесс или, наоборот, подставить второстепенный. -1000 запрещает завершать процесс совсем. Поменять поправку у работающего процесса — choom:
sudo choom -p 1494 -n 300
pid 1494's OOM score adjust value changed from -500 to 300
Чтобы поправка сохранялась после перезапуска, задайте её в настройках службы (OOMScoreAdjust=-500, см. следующий раздел) или контейнера (docker run --oom-score-adj=-500).
Не защищайте от OOM killer всё подряд: если процесс с -1000 сам займёт всю память, ядро будет завершать всё остальное, включая SSH.
systemd-oomd на Ubuntu Server
systemd-oomd — служба, которая завершает процессы раньше ядра, когда давление на память (PSI) держится высоким. В Ubuntu она входит в настольную редакцию: пакет systemd-oomd ставится как зависимость ubuntu-desktop и ubuntu-desktop-minimal.
В Ubuntu Server 24.04 и 26.04 её нет: пакета нет в списке пакетов облачного образа, и пакет ubuntu-server от него не зависит. Проверить на своём сервере:
systemctl status systemd-oomd
Unit systemd-oomd.service could not be found.
Значит, на сервере работает только OOM killer ядра, и искать его следы нужно в журнале ядра, как описано выше.
Как ограничить память службы: systemd MemoryMax
Ограничение не добавляет памяти, но меняет, кто пострадает: вместо случайного крупного процесса (часто базы данных) ядро завершит службу, которая вышла за свой лимит. Остальные продолжат работать.
Ограничение задаётся в дополнительном файле настроек службы, чтобы не править основной файл. Для службы myapp:
sudo mkdir -p /etc/systemd/system/myapp.service.d
sudo tee /etc/systemd/system/myapp.service.d/memory.conf > /dev/null <<'EOF'
[Service]
MemoryMax=300M
OOMScoreAdjust=300
Restart=on-failure
EOF
sudo systemctl daemon-reload
sudo systemctl restart myapp
MemoryMax— жёсткий предел: при превышении ядро завершает процесс службы.OOMScoreAdjust— поправка к очкам для этой службы.Restart=on-failure— systemd запустит службу снова после завершения.
Если на сервере есть подкачка, служба сверх MemoryMax может занять ещё и её. Запретить это — строкой MemorySwapMax=0.
Проверка — в systemctl status myapp появится строка с пределом, а в /proc/<PID>/oom_score_adj — поправка:
Memory: 90.6M (max: 300.0M available: 209.3M peak: 90.6M)
Что происходит, когда служба выходит за предел (journalctl -u myapp -o cat, Ubuntu 24.04):
Started myapp.service - Test app.
myapp.service: A process of this unit has been killed by the OOM killer.
myapp.service: Main process exited, code=killed, status=9/KILL
myapp.service: Failed with result 'oom-kill'.
myapp.service: Scheduled restart job, restart counter is at 1.
Started myapp.service - Test app.
Есть и мягкий порог — MemoryHigh. При его превышении ядро не завершает процессы, а замедляет их и отбирает у службы кеш. В нашем тесте служба с MemoryHigh=40M и без подкачки упёрлась в этот порог и почти перестала работать, но завершена не была. Используйте MemoryHigh вместе с MemoryMax и чуть ниже него — как сигнал, а не как единственное ограничение.
Как ограничить память контейнера Docker
docker run -d --name myapp --memory=300m --memory-swap=300m myimage
Если указать только --memory, контейнер сможет занять столько же ещё в подкачке: по умолчанию --memory-swap равен удвоенному --memory. Одинаковые значения запрещают контейнеру подкачку.
Как это выглядит, проверим на контейнере с пределом 100 МБ, в котором Python выделяет память по 10 МБ:
docker run --name oomtest --memory=100m --memory-swap=100m python:3-slim python -c '
import time
chunks = []
for i in range(1, 1000):
chunks.append(bytearray(10 * 1024 * 1024))
print(f"выделено {i * 10} МБ", flush=True)
time.sleep(0.1)
'
echo "exit code: $?"
docker inspect oomtest --format 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}'
docker rm oomtest
выделено 80 МБ
выделено 90 МБ
exit code: 137
OOMKilled=true ExitCode=137
В журнале ядра при этом — строки Memory cgroup out of memory, которые мы разобрали выше.
В Docker Compose то же самое:
services:
app:
image: myimage
deploy:
resources:
limits:
memory: 300M
memswap_limit: 300M
restart: unless-stopped
Проверка: docker inspect -f '{{.HostConfig.Memory}} {{.HostConfig.MemorySwap}}' <контейнер> покажет 314572800 314572800, а в docker stats в столбце LIMIT — 300MiB.
Подкачка как буфер
Файл подкачки не заменяет память, но даёт серверу запас на пики: при кратковременной нехватке ядро вынесет на диск редко используемые страницы вместо того, чтобы сразу завершать процесс. На серверах с 1–2 ГБ памяти это особенно полезно — например, на время сборки проекта или обновления системы. Как создать файл подкачки, описано в разделе «Файл подкачки» статьи о первой настройке сервера.
Если же si и so в vmstat ненулевые постоянно, а подкачка заполнена, подкачка уже не буфер: сервер работает медленно, потому что постоянно обращается к диску. Это признак того, что памяти нужно больше.
Когда добавлять память
Добавляйте память, если выполняется хотя бы одно из условий:
availableвfree -hв часы нагрузки регулярно ниже 10 % отtotal;- OOM killer срабатывает при обычной нагрузке, а не из-за утечки памяти в одном процессе;
siиsoвvmstatненулевые постоянно, подкачка заполнена;some avg300в/proc/pressure/memoryдержится выше нескольких процентов.
Если же память раз за разом съедает один процесс и растёт без остановки — это утечка памяти. Добавленная память лишь отложит проблему: ограничьте процесс и ищите причину в приложении.
Сколько памяти нужно под типовые задачи, мы посчитали в статье «Сколько оперативной памяти нужно VPS». В Frogus Cloud память можно добавить к работающему серверу; уменьшить её потом нельзя, поэтому добавляйте по замерам, а не впрок.
Если не работает
| Что видно | Что делать |
|---|---|
Служба перезапускается, но в журнале ядра нет out of memory |
Причина не в памяти: смотрите journalctl -u <служба> перед завершением |
sudo dmesg пуст или не показывает старые записи |
Буфер ядра ограничен по размеру и очищается при перезагрузке; ищите в journalctl -k -b -1 — журнал прошлой загрузки |
journalctl -k -g "Out of memory" не находит Memory cgroup out of memory |
Пишите шаблон строчными буквами: -g "out of memory" |
| Завершается база данных, хотя память занимает приложение | Ограничьте приложение через MemoryMax или --memory, базе поставьте отрицательный OOMScoreAdjust |
Служба с MemoryHigh «зависла» |
Она упёрлась в мягкий порог: поднимите MemoryHigh или задайте MemoryMax |
Контейнер завершается с кодом 137, но OOMKilled=false |
Процесс завершили сигналом 9 вручную или по таймауту остановки (docker stop), а не из-за памяти |
Что дальше
- Сколько оперативной памяти нужно VPS: сайт, бот, база данных
- Первая настройка сервера на Ubuntu 24.04 и 26.04 — в том числе файл подкачки
- Как входить на сервер по SSH-ключу
- Закончилось место на диске: как найти и освободить
- Метрики и оповещения о памяти и диске: Prometheus и Grafana