This article explains how the oom killer linux how it intervenes during acute memory shortages and why it forcefully terminates services to keep the server operational. I’ll walk you through, step by step, how I identify triggers, understand the scoring system, and use targeted settings to control behavior under memory pressure.
Key points
- Trigger: OOM events occur when RAM is full, the swap space is exhausted, and reclaim attempts fail.
- Scoring: The kernel assigns a
oom_scoreand kills processes that are using a large amount of memory. - Recognition: For more information, see
dmesgwith „Out of memory“ and „Killed process.“. - Control systemWith
oom_score_adjI prioritize services based on their importance. - Prevention: Monitoring, limits, swap strategy, and leak analysis prevent hard kills.
What is the OOM Killer in the Linux kernel?
The OOM Killer is the last Safety line of the kernel and terminates processes when no more memory pages are available. I view it as a controlled emergency shutdown that prevents a total system failure and immediately frees up RAM. Before doing so, the system attempts to swap pages or flush caches, but if the pressure persists, the only option left is a hard cut. In logs, I can identify this moment by entries such as „Out of memory“ and „Killed process,“ often accompanied by a SIGKILL. This behavior is enabled by default and runs automatically, which often leads to surprises in production environments when critical services disappear without warning.
When does the mechanism kick in?
OOM events occur when physical RAM is almost fully occupied, the swap space can no longer provide a buffer, and requests for new pages fail. In this situation, the kernel assesses whether to reclaim space from the Page cache and swapping still helps, and only launches the killer when the situation is hopeless. Short-term spikes don't trigger the mechanism directly; it requires sustained pressure and futile attempts to free up memory. For the analysis, it helps me to look at RSS, swap usage, the page cache percentage, and the allocations per control group. Anyone who wants a deeper understanding of how cache eviction works can check out the background information on Page Cache Eviction view them and measure the effects on your own system.
How does the kernel select the „victim“?
The selection follows a Scoring, which uses Linux as oom_score calculated per process. A high percentage of total memory, a large amount of RSS, and a non-critical role result in a higher score. System-critical processes such as init receive a discount, while memory-hungry worker processes or caches often rank higher. Via oom_score_adj I can adjust the score in a targeted manner and thereby control the chain of victims. Typically, the kernel terminates the process with the highest score using SIGKILL in order to free up as much RAM as possible in one go.
Identifying Traces: Logs and Signals
After a sudden end to my shift, the first thing I do is check dmesg and the kernel logs. If „Out of memory“ and „Killed process“ appear there, I note down the PID, process name, user, and the calculated score. Often, the application itself lacks an error log because SIGKILL does not allow for a cleanup phase. I cross-reference the timestamps with the monitoring data to track the increase in RAM, swap, and RSS per process. This allows me to reliably and quickly identify leaks, oversized heaps, or missing limits.
Take Control with oom_score and oom_score_adj
Every process has, in /proc/[PID]/oom_score a current Value given his vulnerable situation. With /proc/[PID]/oom_score_adj I can lower or increase the likelihood that the kernel will terminate this process. I protect critical services, such as databases, with a negative adj, and I make unimportant worker processes „sacrificable“ with a positive adj. The change takes effect immediately, which is particularly useful during rollouts or load tests. In this way, I transform an unpredictable emergency mechanism into a tool that follows my priorities.
Typical Hosting Scenarios Under Storage Pressure
In environments with many containers, databases, and caches, I see the OOM killer particularly often. A database that grows unchecked pushes other services out of the RAM and leads to critical crashes. Memory leaks in web applications build up pressure over hours until even memory reclamation is no longer effective. Excessively generous container limits on a host that’s too small further exacerbate the situation. Anyone familiar with these patterns sets up early warnings and takes action before the memory leak takes over.
Best Practices for Avoiding Hard Kills
I plan storage realistically, factor in a buffer for peak loads, and set clear limits for each service. Monitoring tracks RSS, swap usage, and oom_score, so that warnings are triggered before a critical situation arises. In container setups, I set cgroup limits so that individual services don’t dominate the host. A sensible swap strategy handles peaks without permanently slowing down the system. For a deeper understanding and planning, I refer to practical guides on Manage virtual memory, so that workloads have sufficient capacity.
Structured Procedure for Handling an OOM Incident
After the incident, the first thing I do is retrieve the log entries dmesg and kern.log, and organize them chronologically. In the monitoring section, I check the graphs for RAM, swap, RSS, and page cache to see how the load has changed over time. Next, I check ulimits, Cgroup and container limits, as well as application parameters such as JVM heaps. Then I adjust oom_score_adj so that the more important things remain and the non-essential ones are addressed first. Finally, I address the root cause: fixing leaks, limiting caches, reducing concurrency, and properly sizing resources.
Special Considerations in VPS and Cloud Environments
On virtual machines, there is a second layer of limits, such as those imposed by the hypervisor or the orchestration system. I am therefore familiar with the allocated RAM-Set the quantity precisely and configure Kubernetes or container limits accordingly. The Linux OOM Killer continues to function as described, but provider mechanisms may trigger additional throttling. Clear prioritization is particularly helpful when dealing with many pods: Critical deployments are allocated resources, while non-critical jobs run with tighter constraints. Referring to the platform provider’s documentation and conducting your own tests will help prevent surprises during production operations.
Fine-Tuning Memory: Overcommit, Swappiness, and Caches
If you want to manage OOM risks, you need to adjust kernel parameters carefully and understand how they interact. vm.overcommit_memory and vm.overcommit_ratio control how generous Linux is with virtual allocations, while vm.swappiness affects the ratio of swapping to reclaim. vm.vfs_cache_pressure Controls the aggressiveness of flushing inode and dentry caches, thereby directly affecting the available space in the page cache. I always test the effects under realistic Load, log the metrics, and make changes only incrementally. For background information and scenarios, it helps to take a look at Memory Overcommit, so you can choose your own defaults wisely.
| Parameter/Key Figure | Role in the system | Where to check | Usual direction |
|---|---|---|---|
| Free RAM | A buffer against hard kills | free, /proc/meminfo | Keep enough in reserve |
| Swap Usage | Shock Absorbers for Peaks | free, vmstat | Low to moderate |
| vm.overcommit_memory | Virtual Allocation | sysctl | 0/2 depending on the risk |
| vm.overcommit_ratio | Overcommit Quota | sysctl | Tailored to the workload |
| vm.swappiness | Swap Propensity | sysctl | Average Instead of Extreme |
| vm.vfs_cache_pressure | Reclaiming VFS Caches | sysctl | 100 as a starting point |
Global OOM vs. Cgroup OOM: What Exactly Dies?
In modern setups using cgroups (v1/v2), an OOM event can local in a memory cgroup or global triggered on the host. If a process is running in a container with strict memory.max (or limit), the kernel typically terminates only processes in that cgroup („memcg OOM“), while the rest of the system continues to run. In dmesg I can tell by signs such as constraint=CONSTRAINT_MEMCG or references to the affected Cgroup. Only when no Cgroup can provide any more RAM and global memory is exhausted does the system-wide OOM Killer. To ensure stability, I think it's important to set limits so that an overreaching service fails within its own cgroup, rather than bringing down the entire host. In Cgroups v2, I can also use memory.high Set soft throttling limits and use memory.oom.group Specify that, in the event of an emergency, the entire group should terminate—which is cleaner than a half-dead residual process.
Tools and Metrics in Practice
To quickly identify the root cause, I collect reproducible data. These tools help me on a regular basis:
- Process Overview:
ps -eo pid,ppid,cmd,%mem,rss --sort=-rss | headDisplays memory hogs. - Smaps Rollup:
cat /proc//smaps_rollupReturns a process's RSS/PSS/Swap without lengthy parsing. - pmap:
pmap -x | sort -nrk3 | headLists mappings with size and RSS; good for heaps and large segments. - Slab Usage:
slabtop -oShows kernel caches that can grow under heavy load. - System Pressure:
vmstat 1andsar -r 1provide context on paging, swap I/O, and frees. - Cgroup Stats: In v2, I check
/sys/fs/cgroup/memory.current,memory.swap.currentandmemory.statof the affected service.
# Easily Read OOM Logs
dmesg -T | egrep -i 'out of memory|oom-kill|killed process'
# Sort top candidates by oom_score
for p in /proc/[0-9]*; do
pid=${p##*/}
[ -r "$p/oom_score" ] || continue
printf "%6s %5s %-30s\n" \
"$(cat $p/oom_score)" \
"$(cat $p/oom_score_adj 2>/dev/null || echo 0)" \
"$(tr -d '\0' < $p/comm)"
done | sort -nr | head -n 20
If I see repeated OOMs, I document the Baseline these values during normal operation and compare them with the incident window. Any deviations are immediately apparent, such as an unchecked rise in PSS or disproportionately large slabs.
Systemd, Containers, and Orchestration: Targeted Control
In systemd, I set the priority and limits declared in Unit files:
[Service]
# Protect or make the process sacrificable
OOMScoreAdjust=-900
# Hard/soft memory limits (cgroup v2)
MemoryMax=8G
MemoryHigh=6G
# Optional: Limit swap
MemorySwapMax=2G
# Behavior during OOM under systemd
# (e.g., force a restart)
Restart=on-failure
RestartSec=5
In container environments, I ensure that clear limits are set for each service. It is important to me to distinguish between Request (scheduled reservation) and Limit (hard cap). Pods with appropriate requests/limits receive better QoS ratings; „BestEffort“ workloads are at risk of OOM. A practical detail: If the kernel terminates a container due to a cgroup OOM, I often see the exit code 137 and events involving OOMKilled; in the host‑dmesg This can be correlated. In production clusters, I schedule critical deployments as „Guaranteed,“ while batch jobs are intentionally scheduled with tighter time windows and thus are the first to be rescheduled.
Kernel Details: OOM Reaper, THP, and Fragmentation
After the kill, the OOM Reaper: A kernel thread removes the victim process's memory mapping as quickly as possible so that RAM is actually freed up. This explains why memory is sometimes not freed until to is visibly reflected back by the kill entry. At the same time, the Storage Compaction Reaching its limits—if the RAM is heavily fragmented, there are no contiguous blocks available for large allocations (such as with Transparent Huge Pages, THP). THP delivers performance but can make allocations difficult under heavy load. For latency-critical workloads, I disable or limit THP on a trial basis and measure the effects.
Another factor is Slab Caches and the page cache: Under I/O-intensive workloads, these caches grow significantly. With vm.vfs_cache_pressure and targeted reclaim can be used to control their size; I use blanket clearing (drop caches) only as a diagnostic tool, not as a permanent solution. In addition, I pay attention to NUMA: If a memory node is exhausted, a process in that NUMA zone may fail even though there is RAM available globally. Corresponding messages also appear in the kernel logs.
Delving Deeper into Swap Strategies: Swappiness, ZRAM/Zswap, I/O Budget
Swapping isn't a bad thing; it's a Shock Absorber. The key is to use it wisely. With vm.swappiness I adjust how early the kernel begins to use swap. Values that are too low allow the page cache to dominate and can trigger OOMs sooner; values that are too high shift the load to swap I/O and slow down the system. On compact hosts, I like to use ZRAM or Zswap, to create a compressed buffer that absorbs peaks without overloading the disk. It’s important to remember: Swap is not a substitute for missing capacity. It merely buys time so that the OOM killer doesn’t have to kick in at all.
Special Cases: mlock, RLIMITS, and the Pitfalls of Overcommitment
Certain constraints can increase the risk of an OOM error or alter its behavior:
- Locked Storage: Processes that are carried out via
mlock()By pinning pages, you prevent them from being reclaimed. At high rates, this can slow down the Reaper's speed. - RLimits:
RLIMIT_ASandRLIMIT_RSSThey set upper limits per process and prevent individual services from getting out of hand—a key component in preventing OOMs. - Overcommit: Overcommit settings that are too generous allow for a large virtual address space that cannot be physically supported later on. Allocation spikes caused by many threads running simultaneously then lead to memory access failures and accelerate OOM events.
- panic_on_oom: For highly critical systems, there is the option to trigger a kernel panic in the event of an OOM. This is only useful in narrowly defined HA scenarios; otherwise, it is counterproductive.
- „Unkillable“ Is Risky:
oom_score_adj=-1000While it protects against the "killer," it can cause the entire system to freeze. I only use it for absolutely essential, small processes (e.g., init), not for memory-intensive server services.
Practical Application: Setting Priorities and Ensuring Changes Are Implemented
I define a Rankings Services: What needs to stay, and what can go first? I translate this order into oom_score_adj, cgroup limits, and (where available) restart policies. Changes are incorporated as code into unit or deployment manifests, accompanied by monitoring metrics. In load tests, I simulate storage pressure: I increase data volumes, ramp up parallelism, let caches grow—and observe whether exactly the „sacrificable“ processes drop out while core components remain online. Only once this works consistently does the configuration go into production.
Diagnostic Patterns: Distinguishing Between Leaks, Heaps, and Fragmentation
Not every rising RSS is a leak. I make a systematic distinction:
- Leak: RSS/PSS rise steadily, even without an increase in load;
smaps_rollupGrows steadily; GC cycles (in managed runtimes) do not help. - Heap Peaks: RSS rises with load and then falls again; the page cache correlates with I/O patterns.
- Fragmentation: Sufficient free RAM, but large allocations fail; logs show compaction attempts, and THP allocations fail more frequently.
For JVM or Node workloads, I check whether the runtime recognizes the container limits. Heaps or JIT code caches that are oversized can exceed the limit and trigger OOMs, even though there appears to be room to spare. I set heaps, including overhead, so that under MemoryMax there is still some buffer left for native segments, thread stacks, and the page cache.
Playbook for Rollouts and Load Testing Under Storage Pressure
- Measure the baseline: RSS/PSS per service, slab usage, swap ratio, cache sizes,
oom_score. - Setting Boundaries: Memory High/Max or define container limits with a realistic buffer;
OOMScoreAdjustAssigned by priority. - Create stress: Data volume, concurrency, cache growth; record I/O and CPU profiles.
- Observe:
dmesg -T, host and cgroup metrics; check which one comes under pressure first. - Iterate: Adjust limits/adj, adjust swappiness, test THP settings, measure again.
- Automate: Integrate checks into CI/CD, set up alerts for threshold values, and establish restart policies for affected services.
In a nutshell: concrete steps
I understand the OOM Killer to be Signal, ...that my system previously had too little buffer or that processes were prioritized incorrectly. With monitoring, realistic limits, a sound swap strategy, and the deliberate use of oom_score_adj I significantly reduce hard crashes. In production environments, I protect core processes, make peripheral services dispensable, and measure every change. For containers, I strictly set Cgroup limits so that a service doesn’t block the entire host system. Those who maintain this discipline keep Linux responsive even under pressure and significantly reduce the time it takes to identify the root cause.


