How it works
The explanation, with examples you can run.
Reserved is not resident
python3 - <<'EOF'import mmap, os, subprocessdef show(label): out = subprocess.run(["ps", "-o", "vsz=,rss=", "-p", str(os.getpid())], capture_output=True, text=True) print(label, out.stdout.strip())m = mmap.mmap(-1, 1 << 30) # map 1 GiB, touch nothingshow("after mmap 1GiB:")for i in range(0, 200 << 20, 4096): # touch 200 MiB, one byte per page m[i] = 1show("after touching 200MiB:")EOFafter mmap 1GiB: 1064824 10592after touching 200MiB: 1064824 215396Both columns are in KiB. VSZ (virtual size) jumped by 1 GiB at once; RSS (resident set size) grew only when pages were written. The same thing happens when the JVM reserves its maximum heap or Go maps arenas: big VSZ is normal, RSS is what costs RAM. Even RSS overstates things a bit, because shared libraries are counted in every process that maps them; PSS in /proc/<pid>/smaps_rollup shares them out fairly.
The page cache
free -mdd if=/dev/urandom of=/tmp/big bs=1M count=300 status=nonefree -m | head -2 total used free shared buff/cache availableMem: 7835 838 6463 24 729 6997Swap: 1023 0 1023 total used free shared buff/cache availableMem: 7835 837 6160 24 1037 6997Writing a 300 MB file moved about 300 MB from free to buff/cache, while available barely changed. File data you read or write stays in RAM so the next read is fast, and writes are collected as dirty pages and flushed to disk in the background (Dirty: in /proc/meminfo, or call fsync when it must be on disk now). When a program needs memory, the kernel drops clean cached pages first. So on a busy server low free is healthy; low available combined with swapping or OOM kills is the problem.
When memory runs out
If programs need more than RAM plus swap, and the kernel can't reclaim enough cache, it invokes the OOM killer. It scores processes mostly by how much memory they use, adjusted by /proc/<pid>/oom_score_adj (-1000 exempts a process, +1000 makes it the first choice), kills the winner with SIGKILL and logs it to the kernel log (dmesg or journalctl -k shows Out of memory: Killed process ...).
Containers make this local. A memory limit is a cgroup limit, and hitting it triggers the OOM killer inside that cgroup only. This was run on the Mac host against Docker Desktop:
docker run --name oomtest -m 64m --memory-swap 64m ubuntu:24.04 \ bash -c 'head -c 200m /dev/zero | tail > /dev/null'; echo "exit=$?"docker inspect -f '{{.State.OOMKilled}} {{.State.ExitCode}}' oomtest; docker rm oomtestbash: line 1: 8 Broken pipe head -c 200m /dev/zero 9 Killed | tail > /dev/nullexit=137true 137oomtesttail buffers its input looking for the last lines of a 200 MB stream with no newlines, passes the 64 MiB limit and is killed. Exit code 137 plus OOMKilled: true is the signature to look for in Kubernetes too (Reason: OOMKilled). The fix is a higher limit or less memory use, not a retry.
On macOS
macOS also uses virtual memory and a file cache, but reports it differently: vm_stat and Activity Monitor show wired, active, inactive and compressed memory (macOS compresses pages before swapping), and there is no OOM killer in the Linux sense; the system asks apps to free memory and may terminate them under pressure. free doesn't exist on macOS.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.