Skip to content
← ENGINEERING NOTES
LINUX8 min read

Linux OOM Kill With Free RAM Left? Read This

free shows gigabytes available, yet the OOM killer struck. Learn MemAvailable, slab, overcommit and oom_score_adj with commands to find out why and fix it.

BY ASHLIN DARIUS GOVINDASAMY

free says 8 GB available, and yet your database just got shot by the OOM killer. Is Linux lying? No, but it is answering a different question than the one you asked. This guide explains why a Linux box can hit an out-of-memory kill while RAM still looks free, and gives you the commands to read the real numbers: MemAvailable, slab, overcommit accounting and oom_score_adj.

Four different meanings of out of memory

When a process dies with 'out of memory', one of four things usually happened. Knowing which one saves hours of guessing.

  • Real exhaustion: anonymous memory (heap, stacks) plus unreclaimable kernel memory ate everything, and swap, if any, is full.
  • Unreclaimable kernel memory: slab objects that cannot be freed, so the page cache cannot save you.
  • A limit smaller than the machine: a cgroup (container, systemd service) hit its own memory.max, so the system looked fine while the group did not.
  • Strict overcommit: allocations failed because the commit limit was reached, even though pages were physically free.

Step 1: read /proc/meminfo like a detective

The kernel documentation defines MemAvailable as an estimate of how much memory is available for starting new applications without swapping, calculated from MemFree, SReclaimable, the file LRU lists and the low watermarks. It deliberately accounts for the fact that the system needs some page cache to function and that not all reclaimable slab will actually be reclaimable, because some items are in use.

bash
grep -E '^(MemTotal|MemFree|MemAvailable|Cached|Slab|SReclaimable|SUnreclaim|KernelStack|PageTables|CommitLimit|Committed_AS|SwapTotal|SwapFree):' /proc/meminfo

# Convert to GiB for humans
awk '/^(MemAvailable|Slab|SUnreclaim|Committed_AS|CommitLimit):/ {printf "%-14s %6.2f GiB\n", $1, $2/1048576}' /proc/meminfo
The fields that explain most OOM mysteries

Per the same document, Slab is the in-kernel data structure cache, SReclaimable is the part that might be reclaimed (such as caches), and SUnreclaim is the part that cannot be reclaimed under memory pressure. If SUnreclaim is huge and growing, you have a kernel-side leak or a driver hoarding memory, and no amount of dropping caches will help.

Step 2: find who owns the slab

slabtop shows which kernel object caches are fat. Typical culprits are dentry and inode caches after a filesystem walk (reclaimable) or networking and driver objects (often not).

bash
sudo slabtop -o -s c | head -20

# Same data without slabtop (needs root)
sudo awk 'NR>2 {printf "%10.1f MiB  %s\n", $3*$4/1048576, $1}' /proc/slabinfo | sort -rn | head -15
Top slab consumers, sorted by cache size

The vm.vfs_cache_pressure sysctl controls the tendency to reclaim dentry and inode caches. The kernel docs warn that setting it to 0 means dentries and inodes are never reclaimed under memory pressure, which "can easily lead to out-of-memory conditions". If someone 'optimised' it to 0 years ago, you may have found your culprit.

QUICK CHECK

MemAvailable is 6 GiB but SUnreclaim is 5 GiB and climbing. Which action is most likely to help?

Step 3: overcommit, the promise that can be broken

Linux lets processes allocate more virtual memory than exists, betting that most of it will never be touched. The kernel docs describe three modes set by vm.overcommit_memory: 0 is heuristic handling that refuses obvious overcommits and is the default; 1 always overcommits; 2 never overcommits, capping total commit at swap plus a configurable share of RAM.

In mode 2 the limit is visible in /proc/meminfo. The documented formula is CommitLimit = (total RAM pages minus huge TLB pages) times overcommit_ratio / 100, plus swap pages. Committed_AS is what has been promised so far. The docs give an example: 1G of RAM and 7G of swap at a ratio of 30 yields a 7.3G limit. Note the trap: the limit is only enforced in mode 2, so in modes 0 and 1 these numbers are informational.

bash
sysctl vm.overcommit_memory vm.overcommit_ratio
awk '/^CommitLimit:/ {l=$2} /^Committed_AS:/ {c=$2} END {printf "Committed %.1f%% of CommitLimit\n", 100*c/l}' /proc/meminfo

# Try strict mode temporarily, then revert (test on a lab box first)
sudo sysctl -w vm.overcommit_memory=2 vm.overcommit_ratio=80
How close are you to the commit ceiling?

Step 4: how the OOM killer picks a victim

The kernel assigns each candidate a badness score from 0 (never kill) to 1000 (always kill), roughly the proportion of allowed memory the task uses, based on its memory and swap use. If a task uses half its allowed memory, its score is about 500. The 'allowed' memory depends on context: the whole machine, a cpuset, a mempolicy node set, or a memory cgroup limit.

You can read the score and nudge it. oom_score_adj accepts -1000 to +1000 and is added to the badness score. The documentation states that -1000 is equivalent to disabling OOM killing for that task. The value exported in oom_score includes the adjustment, so it ranges effectively over 0 to 2000.

bash
# Top 10 victims right now
for p in /proc/[0-9]*; do
  pid=$(basename "$p")
  [ -r "$p/oom_score" ] && printf "%6s %5s  %s\n" "$(cat $p/oom_score)" "$pid" "$(tr '\0' ' ' < $p/cmdline | cut -c1-60)"
done 2>/dev/null | sort -rn | head -10

# Make the database less attractive, and a batch job more attractive
echo -500 | sudo tee /proc/$(pidof postgres | cut -d' ' -f1)/oom_score_adj
echo 500  | sudo tee /proc/$(pidof my-batch-job)/oom_score_adj
Rank processes by OOM score, then protect or sacrifice

For services, set it declaratively. systemd supports OOMScoreAdjust= in the unit file, and it survives restarts, unlike echoing into /proc.

ini
[Service]
OOMScoreAdjust=-500
# Optional: hard ceiling for this service so it cannot starve the host
MemoryMax=12G
/etc/systemd/system/postgresql.service.d/oom.conf
›Why did my container die when the host had plenty of RAM?

The OOM killer can be invoked inside a cgroup. In that case the allowed memory is the configured limit, and the kernel docs say it will not kill tasks outside that cgroup. cgroup v2 also offers memory.oom.group: when set to 1, all tasks in the cgroup and its descendants are killed together or not at all, treating the workload as indivisible. Check memory.events in the cgroup directory for the oom and oom_kill counters, and read the kernel log with dmesg -T | grep -i 'out of memory' to see whether the constraint was CONSTRAINT_MEMCG or the whole system.

Step 5: read the crime scene

Every kill leaves a report in the kernel log, including a memory summary and a table of tasks with their oom_score_adj. Start there before changing any sysctl.

bash
sudo journalctl -k --since "-2 hours" | grep -iE 'out of memory|oom-kill|Killed process'
sudo dmesg -T | grep -A40 -i 'invoked oom-killer' | head -80
Find and read the last OOM report

QUICK CHECK

A service in a systemd unit with MemoryMax=2G is killed while the host shows 10 GiB MemAvailable. Most likely explanation?

Takeaway: a checklist

  • Read MemAvailable, not MemFree.
  • Compare Slab, SReclaimable and SUnreclaim; use slabtop on the biggest.
  • Check vm.overcommit_memory and, in mode 2, Committed_AS against CommitLimit.
  • Check for cgroup limits and memory.events before blaming the host.
  • Protect critical services with OOMScoreAdjust= and give them sane MemoryMax values.
  • Always read the kernel's OOM report first; it names the constraint.

The 'free says plenty' paradox is rarely a kernel bug. It is a mismatch between a global free-memory number and the specific limit, kind of memory or cgroup that actually ran dry. Measure the right one and the mystery usually solves itself.

SOURCES

  1. The /proc Filesystem: meminfo, oom_score_adj and oom_score — Linux kernel source tree
  2. Overcommit Accounting — Linux kernel source tree
  3. Documentation for /proc/sys/vm/ (overcommit_memory, vfs_cache_pressure, admin_reserve_kbytes, panic_on_oom) — Linux kernel source tree
  4. Control Group v2: memory.oom.group and memory.events — Linux kernel source tree

Frequently asked questions

Why does the OOM killer run when free shows available memory?

The kill may come from a cgroup limit, strict overcommit, or unreclaimable kernel slab rather than global free RAM. Check the OOM report in dmesg to see which constraint applied.

What is the difference between MemFree and MemAvailable?

MemFree is memory nobody is using at all. MemAvailable estimates how much can be made available for new applications without swapping, counting reclaimable cache and slab.

How do I stop the OOM killer from killing an important process?

Set oom_score_adj to a negative value, with -1000 disabling OOM killing for that task, or use OOMScoreAdjust= in the systemd unit. Also give it a sensible memory limit so it cannot starve everything else.

Should I set vm.overcommit_memory to 2?

Only if your software handles failed allocations gracefully and you have sized the commit limit and admin reserve. Otherwise the default heuristic mode is the documented choice for typical systems.

NEWSLETTER

Get the engineering notes in your inbox.

Kernels, GPUs, networks, AI infrastructure — and the things we broke along the way. No spam, one click to leave.

We store only your email address, to send you new articles. See our privacy policy.

TAGS

linuxoom killerovercommitslabmeminfooom_score_adj

RELATED

ENGINEERING NOTES

Things we built.Things we broke.Things we learned.