Garbage Collection in JavaScript verstehen
Garbage Collection ist eine automatische Speicherverwaltung in JavaScript, die nicht mehr benötigte Objekte freigibt und effiziente Speichernutzung sicherstellt.
Einführung in die Garbage Collection
Die Garbage Collection verwaltet den Speicher in JavaScript automatisch. Sie gibt den Speicher frei, der von nicht mehr benötigten Daten belegt wird, sodass Sie Speicher nahezu nie manuell reservieren oder freigeben müssen. Diese Seite erklärt das grundlegende Konzept, auf dem das gesamte System beruht — Erreichbarkeit — und behandelt anschließend den Mark-and-Sweep-Algorithmus, die Speicherlecks, die dennoch auftreten können, sowie die Rolle von WeakMap/WeakSet bei deren Vermeidung.
Das Verständnis dieser Konzepte ist wichtig, weil „automatisch" nicht „lecksicher" bedeutet. Der Collector entfernt nur, was er als unerreichbar nachweisen kann. Wenn Ihr Code eine versteckte Referenz am Leben erhält, bleibt der Speicher für die gesamte Programmlaufzeit belegt.
Erreichbarkeit: das Grundkonzept
Die Engine verfolgt nicht, ob ein object im logischen Sinne „in Verwendung" ist. Sie verfolgt, ob das object erreichbar ist — ob es eine Referenzkette gibt, die von einem Wurzelelement (Root) zu ihm führt.
Roots sind Werte, die die Engine stets beibehält:
- Die lokalen Variablen und Parameter der aktuell ausgeführten Funktion.
- Variablen und Funktionen in der aktuellen Kette verschachtelter Aufrufe.
- Globale Variablen (Eigenschaften von
globalThis/window).
Jedes object, das durch das Verfolgen von Referenzen von einem Root aus erreichbar ist — direkt oder über andere erreichbare Objekte — wird beibehalten. Alles andere ist Garbage.
Beispiel: Eine Referenz hält ein object am Leben
Erklärung: Das object { name: "John" } war über user erreichbar. Das Kopieren der Referenz in admin erzeugt einen zweiten Pfad zu ihm. Das Setzen von user = null entfernt einen Pfad, aber admin zeigt weiterhin auf das object, sodass es erreichbar bleibt und nicht eingesammelt wird.
Verknüpfte Objekte werden dennoch eingesammelt
Ein verbreiteter Irrtum ist, dass Objekte, die aufeinander verweisen, überleben. Das tun sie nicht — entscheidend ist die Erreichbarkeit von einem Root aus, nicht ob Objekte aufeinander zeigen.
Erklärung: obj1 und obj2 bilden einen Zyklus, aber sobald beide Root-Variablen auf null gesetzt werden, gibt es keinen Pfad von einem Root in den Zyklus. Die gesamte Insel wird unerreichbar und kann eingesammelt werden. Deshalb verwenden JavaScript-Engines Erreichbarkeit statt naivem Referenzzählen, das bei Zyklen zu Lecks führen würde.
Wie der Collector arbeitet: Mark-and-Sweep
JavaScript-Engines geben Speicher mit dem Mark-and-Sweep-Algorithmus frei. Er reduziert „dieses object wird nicht mehr benötigt" auf die präzise Frage „dieses object ist nicht mehr erreichbar."
- Markieren (Mark). Ausgehend von den Roots besucht der Collector jedes erreichbare object und markiert es. Anschließend verfolgt er deren Referenzen, markiert diese Objekte und so weiter, bis jedes erreichbare object markiert ist.
- Bereinigen (Sweep). Jedes object, das nicht markiert wurde, ist unerreichbar. Der belegte Speicher wird freigegeben.
Sie können dies nicht manuell auslösen und sollten es auch nicht versuchen — es gibt kein standardmäßiges gc() in der Sprache. Echte Engines (wie V8) verfeinern den Basisalgorithmus mit Optimierungen wie generationaler Sammlung (neue Objekte sterben jung, also häufiger prüfen) und inkrementeller Sammlung (Arbeit in Teile aufteilen, um Pausen zu vermeiden). Das Erreichbarkeitsmodell, das Sie oben kennengelernt haben, bleibt dabei unverändert.
Häufige Ursachen von Speicherlecks
Ein Leak in JavaScript ist schlicht ein object, das erreichbar bleibt, obwohl Ihr Programm fertig damit ist. Der Collector arbeitet korrekt — er kann nur nicht erkennen, dass die Referenz veraltet ist. Achten Sie auf diese Muster.
Vergessene Timer und Intervalle
Ein ausstehender setInterval-Aufruf (oder setTimeout) hält seinen Callback am Leben, und der Callback hält alles am Leben, über das er schließt. Wenn Sie clearInterval nie aufrufen, wird dieser Speicher für die gesamte Lebensdauer der Seite gehalten.
Abgekoppelte DOM-Knoten
Wenn Sie ein Element von der Seite entfernen, aber eine Referenz darauf in einer Variablen behalten, kann der Knoten — und sein gesamter Teilbaum — nicht eingesammelt werden.
let detached = document.getElementById('list');
document.body.removeChild(detached);
// The node is gone from the page, but 'detached' still references it,
// so it stays in memory. Release it when done:
detached = null;Verbleibende Event-Listener
Ein Listener, der an ein DOM-Element gebunden ist, hält sowohl das Element als auch den Handler (mit allem, worüber er schließt) erreichbar. Entfernen Sie Listener mit removeEventListener, sobald sie nicht mehr benötigt werden:
<button id="myButton">Click me</button>
<script>
const button = document.getElementById('myButton');
function alertClick() {
alert("Button clicked!");
button.removeEventListener('click', alertClick); // free the listener after first use
}
button.addEventListener('click', alertClick);
</script>Der Button reagiert nur auf den ersten Klick: Der Handler entfernt sich selbst mit removeEventListener und gibt die Referenz frei, sodass sie garbage-collected werden kann.
Globale Caches, die nur wachsen
Ein Cache, der in einem Modul-Level- oder globalen object gespeichert ist, hält jeden Eintrag dauerhaft erreichbar, sofern Sie alte Einträge nicht explizit entfernen. Eine unbegrenzte Map, die als Cache verwendet wird, ist ein klassisches langsames Leak.
Closures, die mehr erfassen als erwartet
Eine Closure hält jede Variable in den Scopes am Leben, auf die sie verweist — auch solche, die sie nie tatsächlich verwendet. Das Zurückgeben einer kleinen inneren Funktion aus einer Funktion mit großen lokalen Variablen kann diese im Speicher fixieren. Halten Sie den erfassten Scope minimal, und lesen Sie variable scope für Details dazu, wie Closures ihre Umgebung beibehalten.
Wie WeakMap und WeakSet helfen
Map und Set halten starke Referenzen auf ihre Schlüssel/Werte, sodass alles, was in ihnen gespeichert ist, erreichbar bleibt. WeakMap und WeakSet halten ihre Schlüssel schwach: Wenn die einzig verbleibende Referenz auf ein object die im WeakMap ist, kann das object dennoch eingesammelt werden, und der Eintrag verschwindet mit ihm.
Dies macht WeakMap ideal zum Verknüpfen zusätzlicher Daten mit Objekten (Caches, Metadaten, DOM-Knoten-Bookkeeping), ohne diese Objekte zum dauerhaften Überleben zu zwingen. Da Einträge jederzeit verschwinden können, sind WeakMap/WeakSet bewusst nicht iterierbar und haben keine size-Eigenschaft.
Best Practices
- Bevorzugen Sie lokale Variablen; sie verlassen automatisch ihren Gültigkeitsbereich und werden einsammelbar, wenn die Funktion zurückkehrt.
- Begrenzen Sie globale Variablen — sie leben so lange wie die Anwendung. Verwenden Sie Module und Blockscope (
let/const). - Löschen Sie Timer (
clearInterval/clearTimeout) und entfernen Sie Event-Listener mitremoveEventListener, sobald sie nicht mehr benötigt werden. - Setzen Sie Referenzen auf abgekoppelte DOM-Knoten und andere große Objekte, mit denen Sie fertig sind, auf null.
- Verwenden Sie
WeakMap/WeakSetfür object-schlüsselbasierte Caches und Metadaten, damit Einträge ihre Schlüssel nicht überleben.
Fazit
Garbage Collection in JavaScript basiert vollständig auf Erreichbarkeit: Die Engine behält jedes object, das sie von einem Root aus erreichen kann, und gibt den Rest mithilfe von Mark-and-Sweep frei, was auch Referenzzyklen behandelt. „Automatisch" lässt Sie dennoch dafür verantwortlich, keine veralteten Referenzen zu halten — vergessene Timer, abgekoppelte DOM-Knoten, immer größer werdende Caches und übermäßig erfassende Closures sind die üblichen Verursacher. Greifen Sie zu WeakMap und WeakSet, wenn Sie Daten mit Objekten verknüpfen möchten, ohne sie am Leben zu erhalten.