Skip to main content
RunBook Academy

LinuxXL · Memory PerformanceOOM killer decisions

OOM killer decisions - who gets killed and why

Advanced⏱ ~10 minbash

What you'll learn

  • Explain how the OOM killer picks victims
  • Use oom_score_adj to influence selection
  • Read OOM kill records
  • Configure OOM behaviour per service

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

Not yet marked complete on this device.

When memory runs out and reclaim fails, the OOM killer runs. The decision of who dies is heuristic but tuneable. This lesson covers how to influence it.

OOM selection

The kernel scores every eligible task with oom_badness() and kills the one with the highest score. 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. The adjustment is scaled so that ±1000 shifts the score by ±100% of total memory — which is why -1000 makes a task effectively unkillable and +1000 makes it the first choice regardless of size.

Read the live values straight from procfs:

# the current score the kernel would use
cat /proc/<pid>/oom_score

# the operator-set adjustment feeding into it
cat /proc/<pid>/oom_score_adj

# rank every process by score
for p in /proc/[0-9]*; do
  printf '%s %s %s\n' "$(cat $p/oom_score 2>/dev/null)" "${p##*/}" "$(cat $p/comm 2>/dev/null)"
done | sort -rn | head

Selection is still not the same as fairness. The kernel aims to free the most memory by killing the fewest tasks, so the largest consumer usually dies — and with cgroup v2, selection happens within the cgroup that hit its limit rather than across the whole host.

oom_score_adj

/proc/<pid>/oom_score_adj is a per-process adjustment from -1000 to 1000:

  • -1000: never kill.
  • -500: kill last.
  • 0: default.
  • +500: kill first.
  • +1000: kill always.
# View
cat /proc/<pid>/oom_score_adj

# Set
echo -500 > /proc/<pid>/oom_score_adj

For systemd services:

[Service]
OOMScoreAdjust=-500    # kill last

For critical services (databases, monitoring agents): set to a low value to avoid being killed. For cache / batch processes: set to a high value.

Read OOM kill records

sudo dmesg | grep -i oom
sudo journalctl -k | grep -i oom

Each kill emits two lines. The oom-kill: line says why the allocation failed; the human-readable line says who died.

# cgroup OOM - one unit went over its own memory.max
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

# global OOM - 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 file-rss:0kB shmem-rss:0kB

Read constraint= before you change anything. CONSTRAINT_MEMCG with a named oom_memcg= means raise that unit’s MemoryMax or make the service use less. CONSTRAINT_NONE with oom_memcg=(null) means the host is over-committed — raising one unit’s limit there makes it worse. cat /sys/fs/cgroup/<unit-path>/memory.events attributes the same kill without the log, and survives rotation.

The record shows the killed process’s PID, name, and memory usage. To find which service:

# Substitute your own values before running:
PID=1234

# Find the systemd unit
ps -o pid,unit | grep 12345
# or
systemctl status "$PID"

OOM in cgroups

With cgroups v2, OOM is contained per cgroup. If a cgroup exceeds its memory.max, the OOM killer runs within that cgroup.

[Service]
MemoryMax=4G
OOMPolicy=continue    # OOMs but service stays running
OOMScoreAdjust=-900   # strongly avoid being killed

OOMPolicy=continue means the kernel attempts to reclaim memory but does not necessarily kill. Useful for services that should survive memory pressure at the cost of slowness.

OOMPolicy=kill (default) means OOM kills a process in the cgroup.

cgroup-aware OOM

For critical services:

[Service]
MemoryMax=4G
OOMScoreAdjust=-900

For disposable services (caches, batch jobs):

[Service]
MemoryMax=8G
OOMScoreAdjust=+500

The cgroup limit + score adjustment together give fine control.

Common patterns

ServiceMemoryMaxOOMScoreAdjust
DatabaseHigh (8-32G)-800 (avoid)
Web serverMedium (1-2G)-100 (slight avoid)
CacheSmall (256-512M)+500 (prefer to kill)
Batch jobGenerous+500 (disposable)

Knowledge check

Knowledge check · 5 questions

  1. Q1. What does OOMScoreAdjust=-900 mean?

  2. Q2. The OOM killer can choose a smaller process ahead of a larger one, because oom_score_adj shifts the badness score by up to 100% of total memory.

  3. Q3. Which of the following are valid OOMScoreAdjust values? Select all that apply.

  4. Q4. Which factors does oom_badness() consider on a modern kernel? Select all that apply.

  5. Q5. A PostgreSQL server with 40 days of uptime, running as root, is killed by the OOM killer while a 30-second Python script on the same host survives. What is the most likely explanation?

Passing score: 75%. Answers are checked in this browser.