Урок по треку Linux 7 мин

Память и OOM killer за 7 минут

free и available, page cache, swap, виртуальная и резидентная память, OOM killer и cgroups.

Самый частый вопрос про память в Linux: «free показывает 200 МБ свободно, это проблема?». Обычно нет. Linux старается использовать всю память, а важно, сколько её доступно.

free и available

free -m
               total   used   free  shared  buff/cache  available
Mem:            7940   5200    210     120        2530       2400
Swap:           2047    100   1947
  • free: память, не занятая ничем. Маленькое значение это норма.
  • buff/cache: page cache (содержимое файлов) и буферы. Ядро отдаст большую часть этой памяти приложениям по первому требованию.
  • available: оценка ядра, сколько можно выделить новым процессам без свопа. Смотреть надо на неё.

Источник всех этих чисел: /proc/meminfo (MemTotal, MemFree, MemAvailable, Cached, Dirty, SwapTotal и другие).

Page cache

Когда процесс читает файл, ядро оставляет его страницы в памяти: следующее чтение пойдёт из RAM. Запись тоже сначала попадает в кеш как «грязные» страницы (Dirty), а на диск уходит позже. Поэтому:

  • после cat bigfile > /dev/null растёт buff/cache, это хорошо;
  • echo 3 > /proc/sys/vm/drop_caches очищает кеш, но на проде это почти всегда вредно: следующие чтения пойдут на диск;
  • базы данных (PostgreSQL) полагаются на page cache, поэтому им нужен запас памяти сверх собственных буферов.

Виртуальная и резидентная память

  • VSZ/VIRT: всё адресное пространство процесса: зарезервированное, отображённые файлы, библиотеки. Может быть гигантским и ничего не значить.
  • RSS/RES: страницы, реально лежащие в физической памяти. Включает разделяемые библиотеки, поэтому сумма RSS всех процессов больше реального потребления.
  • PSS: RSS, где общие страницы поделены между процессами. Честнее для подсчёта (smem, /proc/<pid>/smaps_rollup).

Linux выделяет память лениво: malloc резервирует адреса, а физическая страница появляется при первой записи.

Overcommit

По умолчанию (vm.overcommit_memory = 0) ядро разрешает выделить больше, чем есть, с эвристической проверкой. Программы часто просят много, а используют мало. Обратная сторона: когда все одновременно начинают реально писать в память, её не хватает, и включается OOM killer.

Swap

Своп выгружает редко используемые анонимные страницы на диск. vm.swappiness (по умолчанию 60) задаёт, насколько охотно ядро свопит анонимную память по сравнению с выбросом page cache. Немного занятого свопа не проблема. Проблема, когда своп активен: ненулевые si/so в vmstat на каждом замере. Тогда всё тормозит, ведь доступ к диску на порядки медленнее RAM.

Для баз и сервисов с жёсткими задержками своп часто уменьшают или отключают. В Kubernetes узлы исторически работают без свопа.

OOM killer

Когда память кончилась и освободить нечего, ядро выбирает процесс и убивает его SIGKILL. Выбор по oom_score (видно в /proc/<pid>/oom_score): в основном по размеру потребления, с поправкой oom_score_adj от -1000 (никогда не убивать) до 1000.

Следы в журнале ядра:

dmesg -T | grep -i -E 'killed process|out of memory'
journalctl -k | grep -i oom
Out of memory: Killed process 1310 (java) total-vm:4800000kB, anon-rss:3900000kB, ...

Процесс, убитый так, завершается с кодом 137 (128 + 9).

cgroups: лимит на группу

Контейнеры и сервисы systemd живут в cgroup с лимитом памяти (memory.max в cgroup v2, MemoryMax= в юните, --memory в Docker, limits.memory в Kubernetes). При превышении лимита OOM срабатывает внутри группы, даже если на машине память есть. В Kubernetes это статус OOMKilled.

Учти: в лимит cgroup входит и page cache группы. Ядро сначала пытается освободить кеш, и только если не выходит, убивает процесс.

Утечка или нормальный рост

  • Утечка: RSS процесса растёт без конца и не возвращается после снижения нагрузки.
  • Нормальный рост: прогрев кешей, JIT, аллокатор не отдаёт память ОС сразу (glibc malloc, сборщики мусора).
  • Для JVM и Node.js важно, что лимит кучи (-Xmx, --max-old-space-size) должен быть меньше лимита контейнера: есть ещё стек, метапространство, буферы. Современные JVM сами читают лимит cgroup.

Частые ошибки

  • Паниковать из-за маленького free при большом available.
  • Складывать RSS всех процессов и получать больше, чем есть RAM.
  • Чистить drop_caches по крону.
  • Ставить -Xmx равным лимиту контейнера.
  • Искать причину OOM в логах приложения: его убили SIGKILL, он ничего не успел написать. Смотреть надо dmesg.

Что спросят на собесе

  • Почему free маленький, а это не проблема. Что такое available.
  • Чем VSZ отличается от RSS.
  • Как работает OOM killer и как понять, что он сработал.
  • Что такое swappiness и когда своп вреден.
  • Почему контейнер убит по OOM, хотя на узле свободно 10 ГБ.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.