...

OOM Killer verstehen: Wenn Linux Prozesse beendet

Der Beitrag erklärt, wie der oom killer linux bei akuten Engpässen im Arbeitsspeicher eingreift und warum er Dienste hart beendet, um den Server betriebsbereit zu halten. Ich zeige Schritt für Schritt, wie ich Auslöser erkenne, das Scoring verstehe und mit gezielten Einstellungen das Verhalten unter Speicherdruck steuere.

Zentrale Punkte

  • Auslöser: OOM-Events entstehen bei vollem RAM, erschöpftem Swap und erfolglosen Reclaim-Versuchen.
  • Scoring: Der Kernel vergibt einen oom_score und tötet Prozesse mit hohem Anteil am Speicher.
  • Erkennung: Hinweise finden sich in dmesg mit „Out of memory“ und „Killed process“.
  • Steuerung: Mit oom_score_adj priorisiere ich Dienste gezielt nach Wichtigkeit.
  • Prävention: Monitoring, Limits, Swap-Strategie und Leak-Analyse verhindern harte Kills.

Was ist der OOM Killer im Linux‑Kernel?

Der OOM Killer ist die letzte Sicherheitslinie des Kernels und beendet Prozesse, wenn keine Speicherseiten mehr bereitstehen. Ich sehe ihn als kontrollierten Not-Aus, der einen Totalausfall verhindert und sofort RAM freischaufelt. Vorher versucht das System, Seiten zu swappen oder Caches zu leeren, doch bei anhaltendem Druck bleibt nur noch der harte Schnitt. In Logs erkenne ich diesen Moment an Einträgen mit „Out of memory“ und „Killed process“, häufig begleitet von einem SIGKILL. Das Verhalten ist standardmäßig aktiv und läuft automatisch, was in produktiven Umgebungen oft für Überraschungen sorgt, wenn wichtige Dienste ohne Vorwarnung verschwinden.

Wann greift der Mechanismus ein?

OOM-Events treten auf, wenn physischer RAM fast vollständig belegt ist, der Swap keinen Puffer mehr liefert und Anfragen auf neue Seiten fehlschlagen. In dieser Situation bewertet der Kernel, ob Reclaim aus dem Page‑Cache und Swapping noch hilft, und startet erst bei Aussichtslosigkeit den Killer. Kurzfristige Peaks lösen den Mechanismus nicht direkt aus; es braucht anhaltenden Druck und vergebliche Freigabeversuche. Für die Analyse hilft mir ein Blick auf RSS, Swap-Usage, Page-Cache-Anteil und die Zuteilungen pro Control Group. Wer tiefer verstehen will, wie Cache-Verdrängung wirkt, kann sich die Hintergründe zu Page Cache Eviction ansehen und die Effekte im eigenen System messen.

Wie wählt der Kernel das „Opfer“ aus?

Die Auswahl folgt einem Scoring, das Linux als oom_score je Prozess berechnet. Ein hoher Anteil am Gesamtspeicher, viel RSS und unkritische Rolle führen zu einem größeren Score. Systemnahe Prozesse wie init erhalten einen Rabatt, während speicherhungrige Worker oder Caches häufig vorn liegen. Über oom_score_adj kann ich den Score gezielt verschieben und damit die Opferkette steuern. Typischerweise beendet der Kernel den Prozess mit dem höchsten Score per SIGKILL, um mit einem Schlag möglichst viel RAM freizugeben.

Spuren erkennen: Logs und Signale

Nach einem plötzlichen Dienstende prüfe ich zuerst dmesg und die Kernel-Logs. Tauchen dort „Out of memory“ und „Killed process“ auf, notiere ich PID, Prozessnamen, Benutzer und den berechneten Score. Häufig fehlt in der Applikation selbst ein Fehlerlog, weil SIGKILL keine Aufräumphase erlaubt. Ich gleiche den Zeitpunkt mit dem Monitoring ab, um den Anstieg von RAM, Swap und RSS pro Prozess nachzuvollziehen. So identifiziere ich Leaks, zu große Heaps oder fehlende Limits zuverlässig und zügig.

Kontrolle gewinnen mit oom_score und oom_score_adj

Jeder Prozess besitzt in /proc/[PID]/oom_score einen aktuellen Wert für seine Gefährdungslage. Mit /proc/[PID]/oom_score_adj senke oder erhöhe ich die Wahrscheinlichkeit, dass der Kernel diesen Prozess beendet. Kritische Dienste wie Datenbanken schütze ich mit einem negativen Adj, unwichtige Worker mache ich „opferbar“ mit einem positiven Adj. Die Änderung wirkt sofort, was gerade bei Rollouts oder Lasttests nützlich ist. So forme ich aus einem unberechenbaren Notmechanismus ein Werkzeug, das meiner Priorität folgt.

Typische Hosting-Szenarien unter Speicherdruck

In Umgebungen mit vielen Containern, Datenbanken und Caches sehe ich den OOM Killer besonders oft. Eine Datenbank, die unkontrolliert wächst, verdrängt andere Dienste aus dem RAM und führt zu harten Kills. Memory Leaks in Webanwendungen bauen über Stunden Druck auf, bis kein Reclaim mehr hilft. Zu großzügige Container-Limits auf einem zu kleinen Host verschärfen die Lage zusätzlich. Wer diese Muster kennt, setzt frühzeitig Alarme und greift ein, bevor der Killer das Heft übernimmt.

Best Practices zur Vermeidung harter Kills

Ich plane Speicher realistisch, gebe Puffer für Spitzenlast ein und setze klare Limits pro Dienst. Monitoring erfasst pro Prozess RSS, Swap-Usage und oom_score, damit Warnungen vor dem Ernstfall feuern. In Container-Setups setze ich Cgroup-Grenzen, damit einzelne Services den Host nicht dominieren. Eine sinnvolle Swap-Strategie fängt Peaks ab, ohne das System dauerhaft zu verlangsamen. Für tieferes Verständnis und Planung helfe ich mir mit Praxisleitfäden zu virtuellen Speicher managen, damit Workloads verlässlich Luft haben.

Strukturierter Ablauf bei einem OOM-Vorfall

Nach dem Vorfall sichere ich zuerst Logzeilen aus dmesg und kern.log und ordne sie zeitlich ein. Im Monitoring prüfe ich die Kurven für RAM, Swap, RSS und Page-Cache, um den Druckverlauf zu sehen. Danach checke ich ulimits, Cgroup- und Container-Limits sowie Applikations-Parameter wie Heaps von JVMs. Anschließend passe ich oom_score_adj an, damit Wichtigeres bleibt und Entbehrliches zuerst geht. Zuletzt behebe ich die Ursache: Leaks fixen, Caches begrenzen, Parallelität reduzieren und Kapazitäten korrekt dimensionieren.

Besonderheiten in VPS- und Cloud-Umgebungen

Auf virtuellen Maschinen kommt eine zweite Ebene an Limits hinzu, etwa durch den Hypervisor oder die Orchestrierung. Ich kenne daher die zugeteilte RAM-Menge exakt und setze Kubernetes- oder Container-Limits passend. Der Linux OOM Killer arbeitet weiterhin wie beschrieben, doch Provider-Mechanismen können zusätzliche Drosselungen auslösen. Gerade bei vielen Pods hilft eine klare Priorisierung: Wichtige Deployments erhalten Reserven, unkritische Jobs laufen enger. Dokumentation des Plattformanbieters und eigene Tests verhindern Überraschungen im Produktivbetrieb.

Speicher feinjustieren: Overcommit, Swappiness und Caches

Wer OOM-Risiken beherrschen will, justiert Kernel-Parameter mit Bedacht und versteht ihre Wechselwirkung. vm.overcommit_memory und vm.overcommit_ratio steuern, wie großzügig Linux virtuelle Zuteilungen erlaubt, während vm.swappiness das Verhältnis von Swapping zu Reclaim beeinflusst. vm.vfs_cache_pressure regelt die Aggressivität beim Freigeben von Inode- und Dentry-Caches und wirkt damit direkt auf den Spielraum des Page‑Caches. Ich prüfe Effekte immer unter realistischer Last, logge die Metriken und ändere nur schrittweise. Für Hintergründe und Szenarien hilft ein Blick auf Memory Overcommit, um die eigenen Defaults sinnvoll zu wählen.

Parameter/Kennzahl Rolle im System Wo prüfen Übliche Richtung
Freier RAM Puffer gegen harte Kills free, /proc/meminfo Genug Reserve halten
Swap‑Nutzung Stoßdämpfer für Peaks free, vmstat Niedrig bis moderat
vm.overcommit_memory Virtuelle Zuteilung sysctl 0/2 je nach Risiko
vm.overcommit_ratio Quote für Overcommit sysctl Passend zur Workload
vm.swappiness Swap‑Neigung sysctl Mittelwert statt Extrem
vm.vfs_cache_pressure Reclaim von VFS‑Caches sysctl 100 als Startpunkt

Globaler OOM vs. Cgroup‑OOM: was genau stirbt?

In modernen Setups mit Cgroups (v1/v2) kann ein OOM‑Ereignis lokal in einer Memory‑Cgroup oder global auf dem Host ausgelöst werden. Läuft ein Prozess in einem Container mit striktem memory.max (bzw. Limit), beendet der Kernel typischerweise nur Prozesse in dieser Cgroup („memcg OOM“), während das Gesamtsystem weiterläuft. In dmesg erkenne ich das an Hinweisen wie constraint=CONSTRAINT_MEMCG oder Hinweisen auf die betroffene Cgroup. Erst wenn keine Cgroup mehr RAM hergeben kann und globaler Speicher erschöpft ist, greift der systemweite OOM Killer. Für Stabilität ist mir wichtig, Limits so zu setzen, dass ein übergriffiger Dienst in seiner Cgroup scheitert, statt den ganzen Host mitzureißen. In Cgroups v2 kann ich zusätzlich mit memory.high weiche Drosselungen setzen und mit memory.oom.group definieren, dass im Ernstfall die gesamte Gruppe endet – sauberer als ein halblebiger Restprozess.

Werkzeuge und Metriken in der Praxis

Zur schnellen Ursachenforschung sammle ich reproduzierbare Zahlen. Diese Werkzeuge helfen mir regelmäßig:

  • Prozess‑Übersicht: ps -eo pid,ppid,cmd,%mem,rss --sort=-rss | head zeigt Speicherfresser an.
  • Smaps‑Rollup: cat /proc/<PID>/smaps_rollup liefert RSS/PSS/Swap eines Prozesses ohne langes Parsen.
  • pmap: pmap -x <PID> | sort -nrk3 | head listet Mappings mit Größe und RSS, gut für Heaps und große Segmenten.
  • Slab‑Nutzung: slabtop -o zeigt Kernel‑Caches, die bei hoher Last anschwellen können.
  • Systemdruck: vmstat 1 und sar -r 1 geben Kontext zu Paging, Swap‑I/O und Frees.
  • Cgroup‑Stats: In v2 prüfe ich /sys/fs/cgroup/memory.current, memory.swap.current und memory.stat des betroffenen Services.
# OOM-Spuren komfortabel lesen
dmesg -T | egrep -i 'out of memory|oom-kill|killed process'

# Top-Kandidaten nach oom_score sortieren
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

Wenn ich wiederholt OOMs sehe, dokumentiere ich die Baseline dieser Werte unter Normalbetrieb und vergleiche sie mit dem Vorfallfenster. Abweichungen springen sofort ins Auge, etwa ein ungebremster Anstieg von PSS oder unverhältnismäßig große Slabs.

Systemd, Container und Orchestrierung: gezielt steuern

Unter systemd setze ich die Priorisierung und Grenzen deklariert in Unit‑Dateien:

[Service]
# Prozess schützen oder opferbar machen
OOMScoreAdjust=-900

# Harte/Weiche Speicherlimits (cgroup v2)
MemoryMax=8G
MemoryHigh=6G
# Optional: Swap begrenzen
MemorySwapMax=2G

# Verhalten bei OOM unter systemd
# (z.B. Neustart erzwingen)
Restart=on-failure
RestartSec=5

In Container‑Umgebungen sorge ich für klare Grenzwerte pro Service. Wichtig ist mir die Unterscheidung von Request (geplante Reservierung) und Limit (harte Obergrenze). Pods mit passenden Requests/Limits erhalten bessere QoS‑Einstufungen; „BestEffort“-Workloads sind OOM‑gefährdet. Ein Praxisdetail: Beendet der Kernel einen Container wegen Cgroup‑OOM, sehe ich häufig Exitcode 137 und Ereignisse mit OOMKilled; im Host‑dmesg lässt sich das korrelieren. In produktiven Clustern plane ich kritische Deployments als „Guaranteed“, während Batch‑Jobs bewusst enger laufen und damit zuerst Platz machen.

Kernel‑Details: OOM Reaper, THP und Fragmentierung

Nach dem Kill greift der OOM Reaper: ein Kernel‑Thread entzieht dem Opferprozess so schnell wie möglich sein Speichermapping, damit RAM wirklich frei wird. Das erklärt, warum Speicher manchmal erst nach dem Killeintrag sichtbar zurückkommt. Parallel dazu kann die Speicherkompaktierung an Grenzen stoßen – ist der RAM stark fragmentiert, fehlen zusammenhängende Bereiche für große Allokationen (etwa mit Transparent Huge Pages, THP). THP liefert Performance, kann aber unter Druck Zuteilungen erschweren. Für latenzkritische Workloads deaktiviere oder begrenze ich THP testweise und messe die Auswirkungen.

Ein weiterer Faktor sind Slab‑Caches und der Page‑Cache: Unter I/O‑lastigen Workloads wachsen diese Caches stark. Mit vm.vfs_cache_pressure und gezieltem Reclaim lässt sich ihr Anteil steuern; pauschales Leeren (Drop‑Caches) setze ich höchstens als Diagnoseinstrument ein, nicht als Dauerlösung. Zudem achte ich auf NUMA: Ist ein Speicher‑Knoten erschöpft, kann ein Prozess in dieser NUMA‑Zone trotz global freiem RAM scheitern. Entsprechende Hinweise tauchen ebenfalls in den Kernel‑Logs auf.

Swap‑Strategien vertiefen: Swappiness, ZRAM/Zswap, I/O‑Budget

Swap ist kein Übel, sondern ein Stoßdämpfer. Entscheidend ist, ihn klug einzusetzen. Mit vm.swappiness reguliere ich, wie früh der Kernel in Swap ausweicht. Zu niedrige Werte lassen den Page‑Cache dominieren und können OOMs früher triggern; zu hohe Werte verlagern Druck in Swap‑I/O und machen das System träge. In kompakten Hosts nutze ich gern ZRAM oder Zswap, um einen komprimierten Puffer zu schaffen, der Peaks abfängt, ohne die Platte zu überlasten. Wichtig bleibt: Swap ist kein Ersatz für fehlende Kapazität. Er kauft nur Zeit, damit der OOM‑Killer gar nicht erst zünden muss.

Spezialfälle: mlock, RLIMITS, Overcommit‑Tücken

Einige Randbedingungen verstärken OOM‑Risiken oder verändern das Verhalten:

  • Gesperrter Speicher: Prozesse, die via mlock() Seiten pinnen, entziehen sie dem Reclaim. Bei hoher Rate kann das Reaper‑Tempo ausgebremst werden.
  • RLimits: RLIMIT_AS und RLIMIT_RSS setzen pro Prozess Obergrenzen und verhindern, dass einzelne Dienste ausufern – ein Baustein gegen OOMs.
  • Overcommit: Zu großzügige Overcommit‑Einstellungen erlauben großen virtuellen Adressraum, der später physisch nicht zu decken ist. Gerade Allokationsspitzen vieler Threads führen dann gleichzeitig zu Fehlgriffen und beschleunigen OOM‑Events.
  • panic_on_oom: Für hochkritische Systeme existiert die Option, bei OOM mit einem Kernel‑Panic zu reagieren. Das ist nur in eng definierten HA‑Szenarien sinnvoll und sonst kontraproduktiv.
  • „Unkillbar“ ist riskant: oom_score_adj=-1000 schützt zwar vor dem Killer, kann aber das Gesamtsystem blockieren. Ich setze das nur für absolut essentielle, kleine Prozesse ein (z. B. init), nicht für speicherhungrige Serverdienste.

Praxis: Prioritäten festlegen und Änderungen absichern

Ich definiere im Team eine Rangliste der Dienste: Was muss bleiben, was darf zuerst gehen? Diese Ordnung übersetze ich in oom_score_adj, Cgroup‑Limits und (wo verfügbar) Restart‑Politiken. Änderungen fließen als Code in Unit‑ oder Deployment‑Manifeste, begleitet von Messpunkten im Monitoring. In Lasttests simuliere ich Speicherdruck: Erhöhe Datenmengen, drehe die Parallelität hoch, lasse Caches anwachsen – und beobachte, ob genau die „opferbaren“ Prozesse fallen, während Kernstücke online bleiben. Erst wenn das reproduzierbar funktioniert, kommt die Konfiguration in Produktion.

Diagnosemuster: Leaks, Heaps und Fragmentierung unterscheiden

Nicht jeder steigende RSS ist ein Leak. Ich unterscheide systematisch:

  • Leak: RSS/PSS steigen monoton, auch ohne steigende Last; smaps_rollup wächst gleichmäßig, GC‑Zyklen (bei Managed Runtimes) helfen nicht.
  • Heap‑Spitzen: RSS steigt mit Last und fällt wieder; Page‑Cache korreliert mit I/O‑Mustern.
  • Fragmentierung: Ausreichend freier RAM, aber große Allokationen scheitern; Logs zeigen Kompaktierungsversuche, THP‑Zuteilungen misslingen öfter.

Für JVM‑ oder Node‑Workloads prüfe ich, ob die Runtime die Container‑Limits erkennt. Zu groß dimensionierte Heaps oder JIT‑Code‑Caches können am Limit vorbeiplanen und OOMs provozieren, obwohl noch scheinbar Luft ist. Ich setze Heaps inkl. Overhead so, dass unter MemoryMax noch Puffer für Native‑Anteile, Thread‑Stacks und Page‑Cache bleibt.

Playbook für Rollouts und Lasttests unter Speicherdruck

  1. Baseline messen: RSS/PSS pro Dienst, Slab‑Anteile, Swap‑Quote, Cache‑Größen, oom_score.
  2. Grenzen setzen: MemoryHigh/Max oder Container‑Limits mit realistischem Puffer definieren; OOMScoreAdjust nach Priorität vergeben.
  3. Stress erzeugen: Datenvolumen, Concurrency, Cache‑Wachstum; I/O‑ und CPU‑Profile notieren.
  4. Beobachten: dmesg -T, Host‑ und Cgroup‑Metriken; prüfen, wer zuerst unter Druck gerät.
  5. Iterieren: Limits/Adj justieren, Swappiness anpassen, THP‑Einstellungen testen, erneut messen.
  6. Automatisieren: Checks in CI/CD verankern, Alarme bei Grenzwerten, Neustart‑Politiken für Opferdienste.

Kurz zusammengefasst: handfeste Schritte

Ich verstehe den OOM Killer als Signal, dass mein System vorher zu wenig Puffer hatte oder Prozesse falsch priorisiert waren. Mit Monitoring, realistischen Limits, sauberer Swap-Strategie und einem bewussten Einsatz von oom_score_adj reduziere ich harte Abschüsse deutlich. In produktiven Setups schütze ich Kernprozesse, mache Randdienste entbehrlich und messe jede Änderung. Bei Containern lege ich Cgroup-Grenzen strikt fest, damit ein Dienst nicht das gesamte Hostsystem blockiert. Wer diese Disziplin beibehält, hält Linux auch unter Druck reaktionsfähig und verkürzt die Zeit bis zur Ursache deutlich.

Aktuelle Artikel

Serverrack mit Linux-Systemen und visualisierter Speicherauslastung
Server und virtuelle Maschinen

OOM Killer verstehen: Wenn Linux Prozesse beendet

Erfahre, wie der OOM Killer in Linux bei Speichermangel arbeitet, Prozesse beendet und wie du als Admin in Hosting-Umgebungen Out-of-Memory-Probleme mit dem Fokuskeyword oom killer linux vermeidest.

Linux Server mit systemd Dienstverwaltung im Hosting-Rechenzentrum
Administration

Systemd im Hosting-Alltag: Dienste effizient verwalten

Lerne, wie du mit systemd und systemctl Dienste im Hosting-Alltag effizient verwaltest. Der Beitrag zeigt praxisnah, wie systemd hosting stabiler macht und linux services automatisiert werden.