Begriff
Memory Leak
Warum wichtig?
Dieser Begriff ist ein Knoten im SengakujiWorks-Wissensnetz. Nutze Level 0 für die erste Einordnung, Level 1 für Praxis, Level 2 für technische Struktur und Level 3 für Grenzen, Fallstricke und Expertenkontext.
Dein Programm holt sich RAM ("Ich merke mir dieses Bild"). Es vergisst, den RAM zurückzugeben, obwohl es das Bild nicht mehr braucht. Der RAM füllt sich langsam. Nach 1 Stunde sind 8GB voll. Der PC wird langsam (Swapping auf Festplatte). Nach 2 Stunden stürzt das Programm ab ("Out of Memory"). Ein Memory Leak ist ein Loch im Eimer. Wasser fließt rein, aber fließt an der falschen Stelle raus (oder bleibt drin und läuft über).
Merksatz: Ein Fehler in einem Computerprogramm, bei dem Speicher, der nicht mehr benötigt wird, nicht freigegeben wird, was zu stetig steigendem Speicherverbrauch und schließlich zum Absturz führt.
Erkennen: Task Manager öffnen. Schau dir die "RAM"-Spalte an. Steigt die Zahl stetig an, ohne je zu fallen? -> Leak.
Debuggen: Profiler nutzen (Chrome DevTools -> Memory Tab -> Heap Snapshot). Vergleiche Snapshot A (Start) und Snapshot B (nach 10 Minuten). Welche Objekte sind neu dazugekommen und gehen nicht weg?
1. Die Klassiker in JavaScript
Auch mit Garbage Collector gibt es Leaks! Der GC löscht nur, worauf keine Referenz mehr zeigt.
- The Forgotten Event Listener:
Du fügst einem Button einen
onClickhinzu. Du löschst den Button aus dem DOM. Aber der Event Listener lebt noch im Hintergrund und hält die Referenz auf den Button fest ("Detached DOM Nodes"). - Global Variables:
Du speicherst Daten versehentlich global (
window.data = riesigesArray). Der GC darf globale Dinge nie löschen (könnten ja noch gebraucht werden). - Closures: Eine innere Funktion merkt sich Variablen der äußeren Funktion. Wenn die innere Funktion überlebt, bleiben auch die alten Variablen im RAM gefangen.
2. C++ / C
Hier ist malloc/free manuell.
Leak: Du überschreibst den Pointer.
ptr = malloc(100);
ptr = malloc(200);
Die Adresse der ersten 100 Byte ist verloren. Niemand kann sie je free()en. Sie sind bis zum Neustart "verloren im Weltraum".
3. WeakRef / WeakMap
Lösung in JS für Caches.
Eine WeakMap hält Objekte nur "schwach" fest.
Wenn niemand sonst das Objekt braucht, darf der GC es löschen, obwohl es in der Map steht.
Perfekt um Metadaten zu DOM-Nodes zu speichern, ohne Leaks zu riskieren.
V8 Heap Dumps & Shallow vs. Retained Size
Beim Leak-Detection analysiert man den Speicher-Baum, ähnlich wie einen Dateimanager. Jedes Objekt hat zwei Größen:
- Shallow Size: Die schlichte Größe des Objekts selbst (ein
DocumentNodeStruct hat z. B. nur ein paar KB). - Retained Size: Die Größe des Speichers, den das System freimachen könnte, wenn dieses eine Objekt gelöscht würde. Hat das KB-kleine Array den unglücklichen Pointer auf eine 100 MB riesige Base64 Grafik, ist seine Retained Size 100 MB! Du suchst im Heap Snapshot deshalb immer nach Objekten mit abnormal hoher Retained Size. Sie sind die "Garbage-Dominateure", die den GC blockieren.
Der Linux OOM-Killer (Out-Of-Memory)
Wenn dein Server-RAM physisch voll ist (Swap erschöpft) und deine Node/Java-Instanz immer noch unerbittlich allocatet, greift das Betriebssystem zur Machete: Der OOM Killer.
Der Linux-Kernel hat keine Zeit zu verhandeln. Er scannt seine Prozesse, rechnet den oom_score (wieviel RAM der Prozess verbraucht) multipliziert mit oom_score_adj aus und schickt ein schonungsloses SIGKILL (Kill -9) an den dicksten Fisch. Dein Docker-Container "exited suddenly with Code 137". In System-Logs (dmesg oder /var/log/syslog) findest du den Satz: Killed process 1234 (node) total-vm:847120kB, anon-rss:530232kB. Das ist das sichere Indiz für ein Memory Leak.
ThreadLocal Variablen in Java / Spring
Ein hinterlistiger Leak-Kandidat im Java-Serverbetrieb (z.B. Spring Boot) sind ThreadLocals.
Da Java Web-Requests auf einem Thread-Pool operieren, nutzen Devs ThreadLocal, um Variablen statisch, aber Thread-isoliert abzulegen (z.B. eine Datenbank-Transaction oder ein User Context).
Da Tomcat/Jetty die Threads des Pools nicht terminiert, wenn der Request fertig ist, sondern sie re-used, bleibt der Speicher auf ewig an den Zombie-Thread gebunden, sofern der Entwickler nicht explizit ThreadLocal.remove() triggert! Jeder neue Request lädt Daten in einen Thread, und nach einer Woche brennt die JVM den Heap nieder (Memory Leak durch vergessene Scope-Säuberung).
Quick-Check
Sind Leaks schlimm?
In kurzlebigen Scripts (PHP Request) egal, weil der Speicher nach 0.1s eh freigegeben wird. In langlaufenden Apps (Server, SPA, Desktop-App) tödlich.Browser und Leaks?
Browser sind berüchtigt. Wenn du einen Tab schließt, sollte der RAM frei werden. Früher (IE6) klappte das oft nicht. Heute (Chrome/Firefox) ist es sehr gut, aber SPAs können den RAM eines offenen Tabs zum Platzen bringen.Circular Reference?
Siehe Garbage Collection. In modernen GCs (Mark-and-Sweep) kein Problem mehr. In alten IEs (Ref Counting für DOM) war es die Hauptursache für Leaks.