[ Web Proxy ]
URL:
Viewing: https://developer.mozilla.org/de/docs/Web/JavaScript/Guide/Memory_management [Back]  [Original]

Speicherverwaltung - JavaScript | MDN

Dieser Inhalt wurde automatisch aus dem Englischen bersetzt, und kann Fehler enthalten. Erfahre mehr ber dieses Experiment.

View in English Always switch to English

Speicherverwaltung

Niedrig-levelige Sprachen wie C verfgen ber manuelle Speicherverwaltungs-Primitiven wie malloc() und free(). Im Gegensatz dazu allociert JavaScript automatisch Speicher, wenn Objekte erstellt werden, und gibt ihn frei, wenn sie nicht mehr verwendet werden (Garbage Collection). Diese Automatik kann eine potenzielle Quelle der Verwirrung sein: Sie kann Entwicklern den falschen Eindruck vermitteln, sich nicht um die Speicherverwaltung kmmern zu mssen.

In diesem Artikel

Speicherlebenszyklus

Unabhngig von der Programmiersprache ist der Speicherlebenszyklus praktisch immer derselbe:

  1. Allocieren Sie den bentigten Speicher
  2. Verwenden Sie den allozierten Speicher (lesen, schreiben)
  3. Geben Sie den allozierten Speicher frei, wenn er nicht mehr bentigt wird

Der zweite Teil ist in allen Sprachen explizit. Die ersten und letzten Teile sind in niedrig-leveligen Sprachen explizit, aber in hoch-leveligen Sprachen wie JavaScript meist implizit.

Allokation in JavaScript

Wertinitialisierung

Um den Programmierer nicht mit Allokationen zu belasten, allociert JavaScript automatisch Speicher, wenn Werte initial deklariert werden.

js
const n = 123; // allocates memory for a number
const s = "string"; // allocates memory for a string

const o = {
  a: 1,
  b: null,
}; // allocates memory for an object and contained values

// (like object) allocates memory for the array and
// contained values
const a = [1, null, "str2"];

function f(a) {
  return a + 2;
} // allocates a function (which is a callable object)

// function expressions also allocate an object
someElement.addEventListener("click", () => {
  someElement.style.backgroundColor = "blue";
});

Allokation durch Funktionsaufrufe

Einige Funktionsaufrufe fhren zur Allokation von Objekten.

js
const d = new Date(); // allocates a Date object

const e = document.createElement("div"); // allocates a DOM element

Einige Methoden allocieren neue Werte oder Objekte:

js
const s = "string";
const s2 = s.substring(0, 3); // s2 is a new string
// Since strings are immutable values,
// JavaScript may decide to not allocate memory,
// but just store the [0, 3] range.

const a = ["yeah yeah", "no no"];
const a2 = ["generation", "no no"];
const a3 = a.concat(a2);
// new array with 4 elements being
// the concatenation of a and a2 elements.

Verwendung von Werten

Die Verwendung von Werten bedeutet im Wesentlichen das Lesen und Schreiben im allozierten Speicher. Dies kann durch Lesen oder Schreiben des Wertes einer Variablen oder einer Objekteigenschaft oder sogar durch bergeben eines Arguments an eine Funktion erfolgen.

Freigabe, wenn der Speicher nicht mehr bentigt wird

Die meisten Speicherverwaltungsprobleme treten in dieser Phase auf. Der schwierigste Aspekt dieser Phase ist es zu bestimmen, wann der allozierte Speicher nicht mehr bentigt wird.

Niedrig-levelige Sprachen erfordern vom Entwickler, manuell zu bestimmen, an welchem Punkt im Programm der allozierte Speicher nicht mehr bentigt wird, und diesen freizugeben.

Einige hoch-levelige Sprachen wie JavaScript verwenden eine Form der automatischen Speicherverwaltung, die als Garbage Collection (GC) bekannt ist. Ziel eines Garbage Collectors ist es, die Speicherallokation zu berwachen und festzustellen, wann ein Block allozierter Speicher nicht mehr bentigt wird, und diesen freizugeben. Dieser automatische Prozess ist eine Annherung, da das allgemeine Problem der Bestimmung, ob ein bestimmtes Stck Speicher noch bentigt wird, unentscheidbar ist.

Garbage Collection

Wie oben erwhnt, ist das allgemeine Problem, automatisch zu finden, ob ein Speicher "nicht mehr bentigt wird", unentscheidbar. Daher implementieren Garbage Collector eine eingeschrnkte Lsung fr das allgemeine Problem. Dieser Abschnitt erklrt die Konzepte, die zum Verstndnis der wichtigsten Garbage-Collection-Algorithmen und ihrer jeweiligen Einschrnkungen erforderlich sind.

Referenzen

Das Hauptkonzept, auf das sich Garbage-Collection-Algorithmen sttzen, ist das Konzept der Referenz. Im Kontext der Speicherverwaltung wird gesagt, dass ein Objekt ein anderes Objekt referenziert, wenn das erstgenannte Zugang zum letzteren hat (entweder implizit oder explizit). Beispielsweise hat ein JavaScript-Objekt eine Referenz auf sein Prototype (implizite Referenz) und auf die Werte seiner Eigenschaften (explizite Referenz).

In diesem Kontext wird der Begriff "Objekt" auf etwas erweitert, das breiter ist als regulre JavaScript-Objekte und enthlt auch Funktionsumgebungen (oder die globale lexikalische Umgebung).

Referenzzhlende Garbage Collection

Hinweis: Keine moderne JavaScript-Engine verwendet mehr Referenzzhlung fr Garbage Collection.

Dies ist der naivste Garbage Collection Algorithmus. Dieser Algorithmus reduziert das Problem von der Bestimmung, ob ein Objekt noch bentigt wird, auf die Bestimmung, ob ein Objekt noch von anderen Objekten referenziert wird. Ein Objekt wird als "Garbage" oder sammelbar betrachtet, wenn es keine Referenzen mehr darauf gibt.

Zum Beispiel:

js
let x = {
  a: {
    b: 2,
  },
};
// 2 objects are created. One is referenced by the other as one of its properties.
// The other is referenced by virtue of being assigned to the 'x' variable.
// Obviously, none can be garbage-collected.

let y = x;
// The 'y' variable is the second thing that has a reference to the object.

x = 1;
// Now, the object that was originally in 'x' has a unique reference
// embodied by the 'y' variable.

let z = y.a;
// Reference to 'a' property of the object.
// This object now has 2 references: one as a property,
// the other as the 'z' variable.

y = "mozilla";
// The object that was originally in 'x' has now zero
// references to it. It can be garbage-collected.
// However its 'a' property is still referenced by
// the 'z' variable, so it cannot be freed.

z = null;
// The 'a' property of the object originally in x
// has zero references to it. It can be garbage collected.

Es gibt eine Einschrnkung im Hinblick auf zirkulre Referenzen. Im folgenden Beispiel werden zwei Objekte mit Eigenschaften erstellt, die sich gegenseitig referenzieren, wodurch ein Zyklus entsteht. Sie gehen auer Sichtweite, nachdem der Funktionsaufruf abgeschlossen ist. Zu diesem Zeitpunkt werden sie berflssig und ihr allozierter Speicher sollte freigegeben werden. Der Referenzzhlungsalgorithmus wird sie jedoch nicht als sammelbar betrachten, da jedes der beiden Objekte mindestens eine Referenz hat, die darauf zeigt, was dazu fhrt, dass keines von ihnen zur Garbage Collection markiert wird. Zirkulre Referenzen sind eine hufige Ursache fr Speicherlecks.

js
function f() {
  const x = {};
  const y = {};
  x.a = y; // x references y
  y.a = x; // y references x

  return "azerty";
}

f();

Mark-and-Sweep-Algorithmus

Dieser Algorithmus reduziert die Definition von "ein Objekt wird nicht mehr bentigt" auf "ein Objekt ist nicht erreichbar".

Dieser Algorithmus setzt die Kenntnis einer Menge von Objekten voraus, die als Wurzeln bezeichnet werden. In JavaScript ist die Wurzel das globale Objekt. Der Garbage Collector wird regelmig von diesen Wurzeln aus starten, alle Objekte finden, die von diesen Wurzeln referenziert werden, dann alle von diesen referenzierten Objekte finden usw. Beginnend bei den Wurzeln findet der Garbage Collector somit alle erreichbaren Objekte und sammelt alle nicht erreichbaren Objekte ein.

Dieser Algorithmus stellt eine Verbesserung gegenber dem vorherigen dar, da ein Objekt mit null Referenzen effektiv nicht erreichbar ist. Das Gegenteil gilt jedoch nicht, wie wir an zirkulren Referenzen gesehen haben.

Derzeit beinhalten alle modernen Engines einen Mark-and-Sweep-Garbage-Collector. Alle Verbesserungen, die im Bereich der JavaScript-Garbage-Collection (generational/incremental/concurrent/parallel Garbage Collection) in den letzten Jahren gemacht wurden, sind Implementierungsverbesserungen dieses Algorithmus, aber keine Verbesserungen des Garbage-Collection-Algorithmus selbst noch seiner Reduzierung der Definition, wann "ein Objekt nicht mehr bentigt wird".

Der unmittelbare Vorteil dieses Ansatzes ist, dass Zyklen kein Problem mehr darstellen. Im ersten obigen Beispiel sind nach Rckgabe des Funktionsaufrufs die beiden Objekte nicht mehr von einer Ressource referenziert, die vom globalen Objekt erreichbar ist. Folglich werden sie vom Garbage Collector als nicht erreichbar eingestuft und ihr allozierter Speicher wird freigegeben.

Jedoch bleibt die Unfhigkeit, die Garbage Collection manuell zu steuern. Es gibt Zeiten, in denen es praktisch wre, manuell zu entscheiden, wann und welcher Speicher freigegeben wird. Um den Speicher eines Objekts freizugeben, muss es explizit nicht erreichbar gemacht werden. Es ist auch nicht mglich, programmatisch die Garbage Collection in JavaScript zu triggern und wird wahrscheinlich nie innerhalb der Kernsprache mglich sein, obwohl Engines APIs hinter Opt-in-Flags aussetzen knnen.

Konfiguration des Speicher-Modells einer Engine

JavaScript-Engines bieten typischerweise Flags an, die das Speicher-Modell freilegen. Zum Beispiel bietet Node.js zustzliche Optionen und Werkzeuge, die die zugrunde liegenden V8-Mechanismen zur Konfiguration und Debugging von Speicherproblemen freigeben. Diese Konfiguration ist in Browsern mglicherweise nicht verfgbar, und noch weniger fr Webseiten (via HTTP-Header usw.).

Die maximale Menge des verfgbaren Heap-Speichers kann mit einem Flag erhht werden:

bash
node --max-old-space-size=6000 index.js

Wir knnen auch den Garbage Collector fr das Debugging von Speicherproblemen mit einem Flag und dem Chrome-Debugger freilegen:

bash
node --expose-gc --inspect index.js

Datenstrukturen zur Untersttzung der Speicherverwaltung

Obwohl JavaScript die Garbage Collector API nicht direkt freilegt, bietet die Sprache mehrere Datenstrukturen an, die die Garbage Collection indirekt beobachten und zur Verwaltung der Speichernutzung eingesetzt werden knnen.

WeakMaps und WeakSets

WeakMap und WeakSet sind Datenstrukturen, deren APIs ihren nicht-weak Gegenstcken: Map und Set eng nachempfunden sind. WeakMap erlaubt es Ihnen, eine Sammlung von Schlssel-Wert-Paaren zu verwalten, whrend WeakSet Ihnen erlaubt, eine Sammlung eindeutiger Werte zu verwalten, beide mit performanten Hinzufgen, Lschen und Abfragen.

WeakMap und WeakSet haben ihren Namen vom Konzept der schwach gehaltenen Werte. Wenn x von y schwach gehalten wird, bedeutet das, dass Sie den Wert von x zwar ber y erreichen knnen, der Mark-and-Sweep-Algorithmus x jedoch nicht als erreichbar betrachtet, wenn sonst nichts stark an ihm festhlt. Die meisten Datenstrukturen, auer den hier besprochenen, halten die ihnen bergebenen Objekte fest, sodass Sie sie jederzeit abrufen knnen. Die Schlssel von WeakMap und WeakSet knnen garbage-getrennt werden (bei WeakMap-Objekten wren die Werte dann ebenfalls fr die Garbage Collection geeignet), solange nichts anderes im Programm den Schlssel referenziert. Dies wird durch zwei Eigenschaften sichergestellt:

  • WeakMap und WeakSet knnen nur Objekte oder Symbole speichern. Dies liegt daran, dass nur Objekte garbage-getrennt werden primitive Werte knnen immer geflscht werden (das heit, 1 === 1 aber {} !== {}), wodurch sie fr immer in der Sammlung bleiben. Registrierte Symbole (wie Symbol.for("key")) knnen ebenfalls geflscht werden und sind daher nicht garbage-getrennt, aber Symbole, die mit Symbol("key") erstellt wurden, sind garbage-getrennt. Bekannte Symbole wie Symbol.iterator sind in einer festen Menge vorhanden und whrend der gesamten Lebensdauer des Programms eindeutig, hnlich wie intrinsische Objekte wie Array.prototype, sodass sie ebenfalls als Schlssel erlaubt sind.
  • WeakMap und WeakSet sind nicht iterierbar. Dies verhindert, dass Sie Array.from(map.keys()).length verwenden, um die Lebendigkeit von Objekten zu beobachten, oder einen beliebigen Schlssel zu erhalten, der ansonsten fr die Garbage Collection in Frage kommen sollte. (Garbage Collection sollte so unsichtbar wie mglich sein.)

In typischen Erklrungen von WeakMap und WeakSet (wie der obenstehenden) wird oft impliziert, dass der Schlssel zuerst garbage-getrennt wird, wodurch auch der Wert zur Garbage Collection freigegeben wird. Betrachten Sie jedoch den Fall, dass der Wert den Schlssel referenziert:

js
const wm = new WeakMap();
const key = {};
wm.set(key, { key });
// Now `key` cannot be garbage collected,
// because the value holds a reference to the key,
// and the value is strongly held in the map!

Wenn key als tatschliche Referenz gespeichert wird, wrde dies eine zirkulre Referenz erzeugen und sowohl den Schlssel als auch den Wert unbrauchbar fr die Garbage Collection machen, selbst wenn nichts anderes key referenziert denn wenn key garbage-getrennt wird, bedeutet das, dass zu einem bestimmten Zeitpunkt value.key auf eine nicht existierende Adresse zeigen wrde, was nicht zulssig ist. Um dies zu beheben, sind die Eintrge von WeakMap und WeakSet keine tatschlichen Referenzen, sondern Ephemerons, eine Erweiterung des Mark-and-Sweep-Mechanismus. Barros et al. bieten eine gute Zusammenfassung des Algorithmus (Seite 4). Um einen Abschnitt zu zitieren:

Ephemerons sind eine Verfeinerung von schwachen Paaren, bei denen weder der Schlssel noch der Wert als schwach oder stark klassifiziert werden knnen. Die Konnektivitt des Schlssels bestimmt die Konnektivitt des Wertes, aber die Konnektivitt des Wertes beeinflusst nicht die Konnektivitt des Schlssels. [] wenn die Garbage Collection Untersttzung fr Ephemerons bietet, erfolgt sie in drei Phasen statt in zwei (Mark und Sweep).

Als grobes mentales Modell denken Sie an eine WeakMap als folgende Implementierung:

Warnung: Dies ist kein Polyfill, noch ist es auch nur annhernd so, wie es in der Engine implementiert ist (die in den Garbage Collection Mechanismus eingreift).

js
class MyWeakMap {
  #marker = Symbol("MyWeakMapData");
  get(key) {
    return key[this.#marker];
  }
  set(key, value) {
    key[this.#marker] = value;
  }
  has(key) {
    return this.#marker in key;
  }
  delete(key) {
    delete key[this.#marker];
  }
}

Wie Sie sehen, hlt das MyWeakMap nie tatschlich eine Sammlung von Schlsseln. Es fgt jedem Objekt, das eingefgt wird, einfach Metadaten hinzu. Das Objekt ist dann ber Mark-and-Sweep fr die Garbage Collection geeignet. Daher ist es nicht mglich, ber die Schlssel in einer WeakMap zu iterieren noch die WeakMap zu leeren (da dies ebenfalls auf das Wissen ber die gesamte Schlsselsammlung angewiesen wre).

Fr weitere Informationen zu ihren APIs, siehe den Leitfaden zu den zugeordneten Sammlungen.

WeakRefs und FinalizationRegistry

Hinweis: WeakRef und FinalizationRegistry bieten direkten Einblick in die Garbage Collection Mechanismen. Vermeiden Sie die Nutzung, wo mglich, da die Laufzeitsemantik fast vollstndig ungarantiert ist.

Alle Variablen mit einem Objekt als Wert sind Referenzen auf dieses Objekt. Solche Referenzen sind jedoch stark ihre Existenz verhindert, dass der Garbage Collector das Objekt als fr die Sammlung geeignet markiert. Ein WeakRef ist eine schwache Referenz zu einem Objekt, die es ermglicht, dass das Objekt garbage-getrennt wird, whrend die Mglichkeit besteht, den Inhalt des Objekts whrend seiner Lebenszeit zu lesen.

Ein Anwendungsfall fr WeakRef ist ein Cache-System, das string URLs groen Objekten zuordnet. Wir knnen hierfr keine WeakMap verwenden, da WeakMap-Objekte ihre Schlssel schwach gehalten, aber nicht ihre Werte wenn Sie auf einen Schlssel zugreifen, erhalten Sie deterministisch immer den Wert (da der Zugriff auf den Schlssel bedeutet, dass er noch lebt). Hier ist es in Ordnung, undefined fr einen Schlssel zu erhalten (wenn der entsprechende Wert nicht mehr lebendig ist), da wir ihn einfach neu berechnen knnen, aber wir wollen keine unerreichbaren Objekte im Cache behalten. In diesem Fall knnen wir eine normale Map verwenden, aber mit jedem Wert als WeakRef des Objekts anstelle des tatschlichen Objektwerts.

js
function cached(getter) {
  // A Map from string URLs to WeakRefs of results
  const cache = new Map();
  return async (key) => {
    if (cache.has(key)) {
      const dereferencedValue = cache.get(key).deref();
      if (dereferencedValue !== undefined) {
        return dereferencedValue;
      }
    }
    const value = await getter(key);
    cache.set(key, new WeakRef(value));
    return value;
  };
}

const getImage = cached((url) => fetch(url).then((res) => res.blob()));

FinalizationRegistry bietet einen noch strkeren Mechanismus, um die Garbage Collection zu beobachten. Er ermglicht es Ihnen, Objekte zu registrieren und benachrichtigt zu werden, wenn sie garbage-getrennt werden. Zum Beispiel, fr das oben beschriebene Cache-System, selbst wenn die Blobs selbst fr die Sammlung freigegeben sind, sind die WeakRef-Objekte, die sie enthalten, nicht und im Laufe der Zeit kann die Map viele nutzlose Eintrge ansammeln. Mit einem FinalizationRegistry kann man in diesem Fall Aufrumarbeiten durchfhren.

js
function cached(getter) {
  // A Map from string URLs to WeakRefs of results
  const cache = new Map();
  // Every time after a value is garbage collected, the callback is
  // called with the key in the cache as argument, allowing us to remove
  // the cache entry
  const registry = new FinalizationRegistry((key) => {
    // Note: it's important to test that the WeakRef is indeed empty.
    // Otherwise, the callback may be called after a new object has been
    // added with this key, and that new, alive object gets deleted
    if (!cache.get(key)?.deref()) {
      cache.delete(key);
    }
  });
  return async (key) => {
    if (cache.has(key)) {
      return cache.get(key).deref();
    }
    const value = await getter(key);
    cache.set(key, new WeakRef(value));
    registry.register(value, key);
    return value;
  };
}

const getImage = cached((url) => fetch(url).then((res) => res.blob()));

Aufgrund von Leistungs- und Sicherheitsbedenken gibt es keine Garantie, wann der Callback aufgerufen wird oder ob er berhaupt aufgerufen wird. Er sollte nur fr Aufrumarbeiten und nicht kritische Aufrumarbeiten verwendet werden. Es gibt andere Mglichkeiten fr bestimmtere Ressourcenverwaltung, wie try...finally, das den finally-Block immer ausfhren wird. WeakRef und FinalizationRegistry existieren ausschlielich zur Optimierung der Speichernutzung in langlaufenden Programmen.

Fr weitere Informationen ber die API von WeakRef und FinalizationRegistry, siehe deren Referenzseiten.


Web Proxy Viewer  |  New URL  |  Original Page