Find whom OOM killed and when
Match the time against the service's journal (journalctl -u <service>) to
see what the service was doing before it died.
A kernel mechanism: when memory runs out on the machine or in a cgroup with a limit, it kills a process with SIGKILL.
sudo journalctl -k -o cat | grep 'Killed process'
who was killed
systemctl show app -p Result -p NRestarts
Result=oom-kill
systemctl show app -p MemoryMax -p MemoryPeak
limit and peak
cat /sys/fs/cgroup/system.slice/app.service/memory.events
choom -p 1234
score and adjustment of a process
sudo choom -n -500 -p 1234
protect a process from OOM
free -h
available — the machine's memory reserve
The OOM killer (out of memory) is the part of the kernel
that frees memory when there is nowhere else to get it. First the kernel
evicts the page cache and, if there is swap, rarely used program pages. When
nothing is left to free, the OOM killer picks one process and ends it with
SIGKILL. This signal cannot be caught: the program has no
time to write anything to its log.
Memory runs out in two places:
| Where | When | Kernel log line | Candidates |
|---|---|---|---|
| whole machine | system memory and swap are exhausted | Out of memory: Killed process |
all processes of the machine |
| cgroup | the group hit its own limit | Memory cgroup out of memory: Killed process |
processes of this group |
A group limit is MemoryMax= in a systemd unit or --memory for a
container (see cgroups-namespaces). It protects the rest of the machine:
a service with MemoryMax=64M hits its own 64 MiB while neighbours keep
working. That is why a cgroup OOM happens while free -h shows free memory.
A machine-wide report differs: the oom-kill: line carries
constraint=CONSTRAINT_NONE and global_oom, and the result is
Out of memory: Killed process 1500 (python3) … oom_score_adj:1000. That is
how a python3 that took 400 MiB on a machine without swap ended.
The kernel gives every process a score, oom_score: the more memory it
holds (RSS, swap, page tables), the higher the score. The adjustment
oom_score_adj, from -1000 to 1000, shifts it: -1000 means never touch,
1000 means kill first. The process with the highest score is killed.
systemd sets a service's adjustment from the unit's OOMScoreAdjust=:
systemd-journald has -250. stress-ng gives its worker processes 1000 so
that OOM hits them.
An OOM report in the kernel log runs to dozens of lines: a call stack, the
group's memory, a task list. grep finds the main lines:
The service hog with MemoryMax=64M, where tail piles its input up in
memory, produced these lines:
| Line | What to take from it |
|---|---|
tail invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0 |
who asked for memory at the moment of shortage; another process may become the victim |
memory: usage 65536kB, limit 65536kB, failcnt 597 |
the group hit its 64 MiB limit |
oom-kill:constraint=CONSTRAINT_MEMCG,…,oom_memcg=/system.slice/hog.service,…,task=tail,pid=1216,uid=0 |
CONSTRAINT_MEMCG — shortage in a cgroup; oom_memcg — which group: a unit or a container |
Memory cgroup out of memory: Killed process 1216 (tail) total-vm:70808kB, anon-rss:64768kB, … |
the victim's PID and name; anon-rss — the program memory it held |
Before the last line the kernel prints the group's tasks with their memory in 4 KiB pages (middle columns omitted here):
The kernel log is readable by root and the groups adm and
systemd-journal. Without sudo, journalctl -k prints -- No entries --
and dmesg prints Operation not permitted.
systemd learns about an OOM in a service's group and stops the whole service
(OOMPolicy=stop by default): its other processes get SIGTERM. The
service result is oom-kill:
With Restart=on-failure the service comes back, hits the same limit, and
NRestarts grows. The group's counters are in memory.events. The service
vmhog is stress-ng under MemoryMax=96M with OOMPolicy=continue: it
survives OOM, and stress-ng starts its worker again.
max is how many times the group hit MemoryMax, oom_kill is how many of
its processes the OOM killer ended. The file exists while the service runs.
A process killed with SIGKILL exits with code 137 = 128 + 9 (see
exit-codes). A shell whose child died prints Killed. A
Docker container after OOM shows Exited (137) and OOMKilled: true (see
docker).
Match the time against the service's journal (journalctl -u <service>) to
see what the service was doing before it died.
The limit goes into a drop-in, a file that extends the unit (see drop-in):
MemoryHigh= is a soft threshold: above it the kernel reclaims the group's
memory and slows it down, without killing. stress-ng with 100 MiB under
MemoryHigh=64M finished without OOM, but ran 13 seconds instead of 8. How
much a service uses now and at most is MemoryCurrent and MemoryPeak in
systemctl show.
Only root can set a negative adjustment. It moves the process in the queue but adds no memory: if the important process ate everything itself, OOM takes its neighbours and then the process too.
tail waits for a newline and keeps all input in memory until it hits the
64 MiB of its group. The machine does not suffer. Memory load with a timer
also comes from stress-ng: --vm 1 --vm-bytes 160M --timeout 10s.
systemctl status shows activating (auto-restart) (Result: oom-kill), and
NRestarts grows. The service hits MemoryMax on every start: the limit is
below what it needs, or memory leaks. Compare MemoryPeak with MemoryMax,
raise the limit with a drop-in or find the leak in the application.
SIGKILL lets the program write nothing. Look in the kernel log:
sudo journalctl -k | grep -i 'killed process'. The service journal keeps
Killed from the shell or exit code 137.
The main shell process managed to exit by itself — with code 137 after OOM killed its child — before systemd received the OOM event:
It is the same OOM. Confirm it with the Memory cgroup out of memory line
at that time in journalctl -k.
free -h shows the memory of the whole machine, while the OOM happened in a
cgroup. The group limit is in systemctl show <service> -p MemoryMax or the
memory.max file under /sys/fs/cgroup/…; for a container, in
docker inspect.
The process in the invoked oom-killer line only asked for memory at the
moment of shortage. The victim is chosen by oom_score — usually the
largest process of the group or machine. Compare the victim's anon-rss
with the others in the Tasks state table.
With oom_score_adj=-1000 the process is never touched. If it is the one
that leaks, OOM kills everything around it, up to sshd and the journal.
Keep -1000 for processes whose memory does not grow.
free, available, page cache and swap.-k and a service log -u.OOMKilled.SIGTERM and SIGKILL.Found an inaccuracy? Report an error in the article