В сентябре 2004 года разработчик Томас Хабетс вернулся к рабочей станции и увидел разблокированный экран: OOM Killer убил xlock. Хабетс предложил список процессов, которые ядро не должно трогать даже в самом безнадёжном случае. Патч тогда не приняли, но именно с этого началась работа над тем, чтобы ядро убивало умнее.

OOM Killer — страховка ядра на случай, когда память кончилась, а процессы продолжают её просить. Linux разрешает программам запрашивать больше виртуальной памяти, чем есть на самом деле: malloc() возвращает адрес, а реальные страницы RAM выделяются, только когда процесс начинает их трогать.

Когда свободных страниц не хватает, ядро сначала пытается освободить память: вытесняет файловые страницы из кэша, сбрасывает грязные на диск, выгружает анонимную память в swap. Если не помогло — разрешает убийство. OOM Killer срабатывает не когда память кончилась до нуля, а когда аллокатор уже не может выполнить запрос.

Поведение при выделении памяти задаёт параметр vm.overcommit_memory. По умолчанию стоит 0 — эвристика, «очевидно безумные» запросы ядро отклоняет. Режим 1 разрешает оверкоммит без проверки. Режим 2 — строгий учёт: лимит считается как CommitLimit, и malloc() возвращает NULL, когда он исчерпан.

Кого убьёт ядро, решает функция oom_badness() в файле mm/oom_kill.c. До ядра 2.6.36 эвристика была многофакторной и непредсказуемой: OOM Killer мог прихлопнуть sshd или базу данных. В 2010 году Дэвид Риентджес из Google переписал её: теперь score почти полностью зависит от памяти — складываются RSS, страницы в swap и таблицы страниц ядра.

Внутри memory cgroup «доступной памятью» считается лимит группы, так что процесс, занявший 100 МБ из 128 МБ, получит высокий score, даже если на хосте свободны гигабайты. Кандидата на смерть можно посмотреть в /proc: файлы oom_score и oom_score_adj есть у каждого процесса.

Выбор часто кажется несправедливым, потому что ядро не знает ваш бизнес. База данных, занявшая 12 ГБ, для OOM Killer логичнее sshd с парой мегабайт. В контейнерах действуют два сценария: локальный memcg OOM при достижении лимита cgroup и глобальный — при нехватке на всей ноде.

Ещё пара механизмов появилась позже. В Linux 4.6 добавили oom_reaper — поток ядра, который освобождает анонимные страницы жертвы, не дожидаясь полного завершения процесса. В 4.20 появился интерфейс PSI: он показывает не объём занятой памяти, а время, потерянное из-за нехватки ресурса.