LinuxXXXVII · Resource ManagementOOM killer
OOM killer behaviour - what happens when memory runs out
What you'll learn
- Explain how the OOM killer selects victims
- Configure OOM behaviour per cgroup
- Investigate OOM kills from kernel logs
- Distinguish a kernel OOM kill from a systemd-oomd kill
- Prevent OOM cascades
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
When memory runs out, the kernel kills a process. This is the OOM killer. This lesson covers how it works and how to manage it.
What the OOM killer does
When the kernel cannot reclaim memory:
- It picks a process to kill.
- It sends SIGKILL.
- The process dies.
- Memory is freed.
The picker scores every eligible task with oom_badness() and
kills the highest scorer. The score is almost entirely about
memory footprint:
score ≈ RSS + swap entries + page-table pages
expressed as a fraction of the memory available to the task,
then offset by oom_score_adj.
OOM in cgroups
With cgroups v2, the OOM killer operates per cgroup. If a cgroup exceeds its memory.max, the OOM killer runs within that cgroup, killing the offending process.
The whole-host OOM only triggers when the system has no memory at all (after all cgroup limits are exceeded).
Configure OOM behaviour
OOMPolicy= does not stop the kernel from killing anything.
It only decides what the service manager does after a process
in the unit has already been OOM-killed.
[Service]
# What systemd does AFTER a process in this unit was OOM-killed:
# continue - log it; leave the (now degraded) unit running
# stop - terminate the unit cleanly; it enters a failed state
# kill - kill every remaining process in the unit
# (sets memory.oom.group=1 on the cgroup)
OOMPolicy=stop
Restart=on-failure
OOMScoreAdjust=-500 # lower oom_score (less likely to be chosen)
The default is whatever DefaultOOMPolicy= is set to in
systemd-system.conf, which ships as stop on current
distributions — not kill. The one exception is a unit with
Delegate= turned on (container and slice managers), where the
default is continue. Confirm on the host rather than assuming:
systemctl show --property=DefaultOOMPolicy
systemctl show myapp.service --property=OOMPolicy
OOMScoreAdjust shifts the oom_score by the given amount.
Negative values make the process less likely to be killed. Note
the limit: when a cgroup OOM fires and every task in that
cgroup carries the same adjustment, the adjustment does not
influence which of them is chosen. It biases selection between
processes with different scores, so it is a host-level tool, not a
within-service one.
Per cgroup (in systemd):
[Slice]
MemoryMax=4G
MemoryHigh=3G
MemoryHigh is the directive that actually reduces the chance of
an OOM kill. Above it the kernel throttles the cgroup and reclaims
aggressively; the service slows down instead of dying.
MemoryMax is the hard wall where the cgroup OOM killer runs.
Setting MemoryHigh below MemoryMax gives the service a
pressure zone to recover in.
The second OOM killer: systemd-oomd
On Ubuntu 22.04 and later, and on Fedora, systemd-oomd is enabled
by default. It is a userspace OOM killer that watches PSI
pressure and terminates services before the kernel has to act.
Its kills leave nothing in the kernel log.
# Is the userspace OOM killer active?
systemctl is-active systemd-oomd
journalctl -u systemd-oomd --since '1 hour ago'
oomctl # what oomd monitors, and current pressure
# Opt a unit in or out of systemd-oomd management
[Service]
ManagedOOMMemoryPressure=kill
ManagedOOMMemoryPressureLimit=50%
ManagedOOMSwap=auto
Find OOM kills
# Kernel logs
sudo dmesg | grep -i 'out of memory\|oom'
# systemd journal
sudo journalctl -k | grep -i 'out of memory\|oom'
# Per-service
sudo journalctl -u my-service | grep -i 'killed\|oom'
# The userspace killer - checked SECOND, and it leaves no kernel log
sudo journalctl -u systemd-oomd --since '1 hour ago'
# Audit
sudo ausearch -m SERVICE_STOP
Every kill emits two lines. The oom-kill: line carries the
evidence; the human-readable line carries the victim.
# a cgroup hit its own memory.max - the host had memory to spare
oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=myapp.service,
mems_allowed=0,oom_memcg=/system.slice/myapp.service,
task_memcg=/system.slice/myapp.service,task=java,pid=12345,uid=0
Memory cgroup out of memory: Killed process 12345 (java) total-vm:8234560kB anon-rss:4123456kB
# the host itself ran out
oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,
oom_memcg=(null),task_memcg=/system.slice/myapp.service,task=java,pid=12345,uid=0
Out of memory: Killed process 12345 (java) total-vm:8234560kB anon-rss:4123456kB
Prevent OOM cascades
A service that grows until it OOMs can affect other services. With cgroups, the OOM is contained.
Without cgroups:
- Service X grows.
- Host memory runs out.
- Host OOM killer runs.
- Service Y (innocent) gets killed.
With cgroups:
- Service X grows.
- X’s cgroup hits memory.max.
- X’s cgroup OOM killer runs.
- X is killed.
- Service Y survives.
The discipline: every service has a cgroup with a memory limit.
Knowledge check
Knowledge check · 5 questions
Q1. How does the OOM killer choose a victim on a modern kernel?
Q2. cgroups contain OOM kills.
Q3. Which of the following help prevent OOM cascades? Select all that apply.
Q4. A colleague sets OOMPolicy=continue on a critical API service, saying it now survives memory pressure. What actually happens when the cgroup runs out of memory?
Q5. On Ubuntu 24.04 a service vanished overnight. journalctl -k | grep oom-kill returns nothing and the application log ends mid-request. What is the next command?
Passing score: 75%. Answers are checked in this browser.