Heim  >  Artikel  >  Web-Frontend  >  Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

coldplay.xixi
coldplay.xixinach vorne
2020-12-09 17:12:492658Durchsuche

In der Spalte „Javascript“ wird ein weiteres wichtiges Thema besprochen: Speicherverwaltung

Wir werden einen weiteren An besprechen Ein wichtiges Thema, das Entwickler aufgrund der zunehmenden Reife und Komplexität der täglich verwendeten Programmiersprachen häufig vernachlässigen, ist die Speicherverwaltung. Wir geben auch einige Tipps zum Umgang mit Speicherlecks in JavaScript. Wenn Sie diese Tipps in SessionStack befolgen, stellen Sie sicher, dass SessionStack keine Speicherlecks verursacht und den Speicherverbrauch unserer integrierten Webanwendungen nicht erhöht. Wenn Sie weitere hochwertige Artikel lesen möchten, klicken Sie bitte auf den GitHub-Blog. Hunderte hochwertige Artikel warten jedes Jahr auf Sie!

ÜbersichtEinführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen SpeicherlecksProgrammiersprachen wie C verfügen über Speicherverwaltungsprimitive auf niedriger Ebene wie malloc() und free(). Entwickler verwenden diese Grundelemente, um dem Betriebssystem explizit Speicher zuzuweisen und freizugeben.

JavaScript reserviert Speicher für Objekte (Objekte, Zeichenfolgen usw.), wenn diese erstellt werden, und gibt den Speicher „automatisch“ frei, wenn sie nicht mehr verwendet werden. Dieser Vorgang wird als Garbage Collection bezeichnet. Diese scheinbar „automatische“ Freigabe von Ressourcen sorgt für Verwirrung, da sie den Entwicklern von JavaScript (und anderen Hochsprachen) den falschen Eindruck vermittelt, dass ihnen die Speicherverwaltung egal ist. Das ist ein großer Fehler.

Auch wenn Entwickler mit Hochsprachen arbeiten, sollten sie sich mit der Speicherverwaltung auskennen (oder zumindest die Grundlagen kennen). Manchmal gibt es Probleme mit der automatischen Speicherverwaltung (z. B. Fehler im Garbage Collector oder Implementierungsbeschränkungen usw.), und Entwickler müssen diese Probleme verstehen, damit sie richtig behandelt werden können (oder eine geeignete Lösung finden, die mit minimalem Wartungsaufwand gewartet werden kann Aufwandscode). Der Lebenszyklus des SpeichersEgal welche Programmiersprache verwendet wird, der Lebenszyklus des Speichers ist derselbe:

Hier finden Sie eine kurze Einführung in jede Phase des Speicherlebenszyklus:


Zuordnen Speicher – Speicher wird vom Betriebssystem zugewiesen, sodass Ihr Programm ihn verwenden kann. In einer Low-Level-Sprache (wie C) ist dies eine explizit ausgeführte Operation, die der Entwickler selbst durchführen muss. In Hochsprachen weist das System jedoch automatisch die intrinsischen Sprachen zu.

Genutzter Speicher

– Dies ist der Speicher, der zugewiesen wird, bevor das Programm ihn tatsächlich verwendet. Lese- und Schreibvorgänge erfolgen, wenn zugewiesene Variablen im Code verwendet werden.

Speicher freigeben

– Geben Sie den gesamten ungenutzten Speicher frei, sodass er freier Speicher wird und wiederverwendet werden kann. Ebenso wie die Zuweisung von Speicheroperationen muss diese Operation auch in Low-Level-Sprachen explizit ausgeführt werden.

Was ist Speicher?

Bevor wir Speicher in JavaScript vorstellen, werden wir kurz besprechen, was Speicher ist und wie er funktioniert.

Auf Hardwareebene wird der Computerspeicher durch eine große Anzahl von Triggern zwischengespeichert. Jedes Flip-Flop enthält mehrere Transistoren, die ein Bit speichern können, und einzelne Flip-Flops sind durch eine eindeutige Kennung adressierbar, sodass wir sie lesen und überschreiben können. Daher kann der gesamte Computerspeicher konzeptionell als ein riesiges Array betrachtet werden, das gelesen und beschrieben werden kann. Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Als Menschen sind wir nicht sehr gut darin, in Bits zu denken und zu rechnen, deshalb organisieren wir sie in größeren Gruppen, die zusammen zur Darstellung von Zahlen verwendet werden können. 8 Bits werden als 1 Byte bezeichnet. Neben Bytes gibt es auch Wörter (manchmal 16 Bit, manchmal 32 Bit).

Vieles ist im Speicher gespeichert:
  • Alle Variablen und anderen vom Programm verwendeten Daten.
  • Programmcode, einschließlich Betriebssystemcode.
  • Der Compiler und das Betriebssystem übernehmen den Großteil der Speicherverwaltung für Sie, aber Sie müssen dennoch die zugrunde liegende Situation verstehen, um ein tieferes Verständnis der zugrunde liegenden Verwaltungskonzepte zu erhalten.
  • Beim Kompilieren von Code kann der Compiler grundlegende Datentypen überprüfen und im Voraus berechnen, wie viel Speicher sie benötigen. Anschließend wird dem Programm die erforderliche Größe im Call-Stack-Bereich zugewiesen. Der Bereich, in dem diese Variablen zugewiesen werden, wird als Stack-Bereich bezeichnet. Denn wenn Funktionen aufgerufen werden, wird ihr Speicher zusätzlich zum vorhandenen Speicher hinzugefügt, und wenn sie beendet werden, werden sie in der LIFO-Reihenfolge (Last-In-First-Out) entfernt. Zum Beispiel:

Der Compiler kennt sofort den benötigten Speicher: 4 + 4×4 + 8 = 28 Bytes.

Dieser Code zeigt die Speichergröße an, die von Ganzzahlvariablen und Gleitkommavariablen mit doppelter Genauigkeit belegt wird. Aber vor etwa 20 Jahren belegten Integer-Variablen normalerweise 2 Bytes, während Gleitkommavariablen mit doppelter Genauigkeit 4 Bytes belegten. Ihr Code sollte nicht von der Größe des aktuellen primitiven Datentyps abhängen.

Der Compiler fügt Code ein, der mit dem Betriebssystem interagiert, und weist die Anzahl der Stapelbytes zu, die zum Speichern von Variablen erforderlich sind.

Im obigen Beispiel kennt der Compiler die genaue Speicheradresse jeder Variablen. Tatsächlich wird jedes Mal, wenn wir in die Variable n schreiben, diese intern in Informationen wie "Speicheradresse 4127963" umgewandelt. n 时,它就会在内部被转换成类似“内存地址4127963”这样的信息。

注意,如果我们尝试访问 x[4]

Beachten Sie, dass, wenn wir versuchen, auf x[4] zuzugreifen, auf die mit m verknüpften Daten zugegriffen wird. Dies liegt daran, dass beim Zugriff auf ein nicht vorhandenes Element im Array (das 4 Byte größer ist als das letzte tatsächlich zugewiesene Element im Array, x[3]) möglicherweise einige m Bits gelesen (oder überschrieben) werden. Dies wird sicherlich unvorhersehbare Auswirkungen auf den Rest des Programms haben.

Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Wenn Funktionen andere Funktionen aufrufen, erhält jede Funktion ihren eigenen Block auf dem Aufrufstapel. Es enthält alle lokalen Variablen, verfügt aber auch über einen Programmzähler, der sich während der Ausführung daran erinnert, wo es sich befindet. Wenn die Funktion abgeschlossen ist, wird ihr Speicherblock an anderer Stelle erneut verwendet.

Dynamische Zuweisung

Leider wird es etwas kompliziert, wenn man zum Zeitpunkt der Kompilierung nicht weiß, wie viel Speicher eine Variable benötigt. Angenommen, wir möchten Folgendes tun:

Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Zur Kompilierungszeit weiß der Compiler nicht, wie viel Speicher das Array verwenden muss, da dies durch den vom Benutzer bereitgestellten Wert bestimmt wird.

Daher kann kein Platz für Variablen auf dem Stapel reserviert werden. Stattdessen muss unser Programm zur Laufzeit explizit den entsprechenden Speicherplatz vom Betriebssystem anfordern. Dieser Speicher wird aus dem Heap-Speicherplatz

zugewiesen. Der Unterschied zwischen statischer Speicherzuweisung und dynamischer Speicherzuweisung ist in der folgenden Tabelle zusammengefasst: Statische Speicherzuweisung Dynamische Speicherzuweisung Die Größe muss zur Kompilierzeit bekannt sein Die Größe muss nicht bekannt sein zur Kompilierzeit bekannt sein Zur Kompilierungszeit ausgeführtZur Laufzeit ausgeführtAuf dem Stapel zugewiesenAuf dem Heap zugewiesenFILO (zuerst rein, zuletzt raus)Keine spezifische Reihenfolge von Zuteilung
🎜

Um die Funktionsweise der dynamischen Speicherzuweisung vollständig zu verstehen, müssen Sie sich mehr mit Zeigern befassen, was möglicherweise zu stark vom Thema dieses Artikels abweicht. Ich werde das entsprechende Wissen über Zeiger hier nicht im Detail vorstellen.

Speicher in JavaScript zuweisen

Jetzt wird der erste Schritt erklärt: Wie man Speicher in JavaScript zuweist.

JavaScript entlastet Entwickler von der Verantwortung für die manuelle Speicherzuweisung – JavaScript weist Speicher zu und deklariert Werte selbst.

Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Bestimmte Funktionsaufrufe führen auch zur Speicherzuweisung von Objekten:

Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Methoden können neue Werte oder Objekte zuordnen:

Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Speicher in JavaScript verwenden

Zugeordneten Speicher in JavaScript verwenden Bedeutet Lesen und Schreiben darin, was durch Lesen oder Schreiben des Werts einer Variablen oder Objekteigenschaft oder durch Übergeben von Parametern an eine Funktion erreicht werden kann.

Speicher freigeben, wenn er nicht mehr benötigt wird.

Die meisten Speicherverwaltungsprobleme treten in dieser Phase auf.

Der schwierigste Teil hierbei ist die Bestimmung, wann der zugewiesene Speicher nicht mehr benötigt wird. Normalerweise muss der Entwickler bestimmen, wo sich der Speicher befindet nicht mehr benötigt und geben Sie es frei.

Hochsprachen integrieren einen Mechanismus namens Garbage Collector, dessen Aufgabe darin besteht, die Speicherzuweisung und -nutzung zu verfolgen, um jederzeit zu erkennen, wann ein Teil des zugewiesenen Inneren nicht mehr benötigt wird. In diesem Fall wird dieser Speicher automatisch freigegeben.

Leider handelt es sich bei diesem Vorgang nur um eine grobe Schätzung, da es schwierig ist zu wissen, ob ein bestimmtes Stück Speicher wirklich benötigt wird (kann nicht algorithmisch gelöst werden).

Die meisten Garbage Collectors arbeiten, indem sie Speicher sammeln, auf den nicht mehr zugegriffen wird, d. h. alle Variablen, die darauf verweisen, sind außerhalb des Gültigkeitsbereichs. Allerdings ist dies eine Unterschätzung des Speicherplatzes, der gesammelt werden kann, da es an jedem Punkt eines Speicherorts immer noch eine Variable im Gültigkeitsbereich geben kann, die darauf verweist, auf die jedoch nie wieder zugegriffen wird.

Garbage Collection

Da es unmöglich ist, festzustellen, ob ein Teil des Speichers wirklich nützlich ist, hat sich der Garbage Collector eine Möglichkeit überlegt, dieses Problem zu lösen. In diesem Abschnitt werden die wichtigsten Garbage-Collection-Algorithmen und ihre Einschränkungen erläutert und erläutert.

Speicherreferenzen

Garbage-Collection-Algorithmen basieren hauptsächlich auf Referenzen.

Im Zusammenhang mit der Speicherverwaltung spricht man von einem Objekt, das auf ein anderes Objekt verweist, wenn es Zugriff auf ein anderes Objekt hat (entweder implizit oder explizit). Beispielsweise verfügt ein JavaScript-Objekt über Verweise auf seinen Prototyp (implizite Referenz) und Eigenschaftswerte (explizite Referenz).

In diesem Zusammenhang wird das Konzept des „Objekts“ auf einen größeren Bereich als normale JavaScript-Objekte erweitert und umfasst auch den Funktionsbereich (oder den globalen lexikalischen Bereich).

Der lexikalische Gültigkeitsbereich definiert, wie Variablennamen innerhalb verschachtelter Funktionen aufgelöst werden: Die innere Funktion enthält die Auswirkung der übergeordneten Funktion, auch wenn die übergeordnete Funktion zurückgegeben wurde.

Referenzzählender Garbage-Collection-Algorithmus

Dies ist der einfachste Garbage-Collection-Algorithmus. Wenn kein Verweis auf ein Objekt vorhanden ist, gilt das Objekt als „Garbage Collector“, wie im folgenden Code:

Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Schleifen können Probleme verursachen

Bei Schleifen gibt es eine Grenze. Im folgenden Beispiel werden zwei Objekte erstellt, die aufeinander verweisen und so eine Schleife erzeugen. Nachdem der Funktionsaufruf den Gültigkeitsbereich verlassen hat, sind sie praktisch nutzlos und können freigegeben werden. Der Referenzzählalgorithmus geht jedoch davon aus, dass keines von ihnen durch Garbage Collection erfasst werden kann, da jedes Objekt mindestens einmal referenziert wird.

Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Mark-and-Sweep-Algorithmus

Dieser Algorithmus kann bestimmen, ob auf ein Objekt zugegriffen werden kann, um festzustellen, ob das Objekt nützlich ist:

  1. Garbage Collector Konstruieren Sie eine „Wurzel“. " Liste zur Aufnahme referenzierter globaler Variablen. In JavaScript ist das „window“-Objekt eine globale Variable, die als Wurzelknoten verwendet werden kann.
  2. Der Algorithmus überprüft dann alle Wurzeln und ihre Kinder und markiert sie als aktiv (was bedeutet, dass sie kein Müll sind). Jeder Ort, den die Wurzeln nicht erreichen können, wird als Müll markiert.
  3. Schließlich gibt der Garbage Collector alle Speicherblöcke frei, die nicht als aktiv markiert sind, und gibt diesen Speicher an das Betriebssystem zurück.

Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Dieser Algorithmus ist besser als der vorherige Algorithmus, da „auf ein Objekt nicht verwiesen wird“ bedeutet, dass auf das Objekt nicht zugegriffen werden kann.

Seit 2012 verfügen alle modernen Browser über Garbage Collectors, die Markierungen löschen. Alle in den letzten Jahren im Bereich der JavaScript-Garbage Collection (generationelle/inkrementelle/gleichzeitige/parallele Garbage Collection) vorgenommenen Verbesserungen waren Implementierungsverbesserungen des Algorithmus (Mark-Sweep) und keine Verbesserungen des Garbage Collection-Algorithmus selbst Ist es das Ziel, festzustellen, ob ein Objekt zugänglich ist?

In diesem Artikel erfahren Sie mehr über die Verfolgung der Garbage Collection, einschließlich des Mark-Sweep-Algorithmus und seiner Optimierungen.

Schleifen sind kein Problem mehr

Im ersten Beispiel oben werden die beiden Objekte nach der Rückkehr des Funktionsaufrufs nicht mehr von Objekten referenziert, auf die vom globalen Objekt aus zugegriffen werden kann. Daher sind sie für den Garbage Collector nicht zugänglich.

Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Obwohl es Referenzen zwischen Objekten gibt, sind sie vom Wurzelknoten aus nicht erreichbar.

Kontraintuitives Verhalten von Garbage Collectors

Obwohl Garbage Collectors praktisch sind, haben sie ihre eigenen Kompromisse, von denen einer der Nichtdeterminismus ist. Mit anderen Worten: GC ist unvorhersehbar und man kann nicht wirklich sagen, was wird passieren. Das bedeutet, dass das Programm in manchen Fällen mehr Speicher belegt, als eigentlich nötig ist. Bei besonders geschwindigkeitsempfindlichen Anwendungen kann es zu kurzen Pausen kommen. Wenn kein Speicher zugewiesen ist, ist der größte Teil des GC im Leerlauf. Schauen Sie sich das folgende Szenario an:

  1. Weisen Sie einen ziemlich großen Satz Innereien zu.
  2. Die meisten (oder alle) dieser Elemente sind als nicht zugänglich markiert (vorausgesetzt, der Verweis verweist auf einen Cache, der nicht mehr benötigt wird).
  3. Keine weitere Zuteilung

In diesen Szenarien werden die meisten GCs nicht weiter sammeln. Mit anderen Worten: Selbst wenn unzugängliche Referenzen zur Sammlung verfügbar sind, wird der Sammler sie nicht deklarieren. Hierbei handelt es sich nicht unbedingt um Lecks, sie können jedoch dennoch zu einer überdurchschnittlichen Speichernutzung führen.

Was ist ein Speicherverlust?

Im Wesentlichen kann ein Speicherverlust definiert werden als: Speicher, der von einer Anwendung nicht mehr benötigt wird und aus irgendeinem Grund nicht an das Betriebssystem oder den freien Speicherpool zurückgegeben wird.

Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Programmiersprachen unterstützen verschiedene Speicherverwaltungsmethoden. Ob ein bestimmter Teil des Speichers verwendet werden soll, ist jedoch tatsächlich eine unbestimmte Frage. Mit anderen Worten: Nur der Entwickler kann feststellen, ob ein Stück Speicher an das Betriebssystem zurückgegeben werden kann.

Einige Programmiersprachen unterstützen Entwickler, andere erwarten von Entwicklern ein klares Verständnis dafür, wann Speicher nicht mehr verwendet wird. Wikipedia bietet einige großartige Artikel zur manuellen und automatischen Speicherverwaltung.

Vier häufige Speicherlecks

1. Globale Variablen

JavaScript behandelt nicht deklarierte Variablen auf interessante Weise: Für nicht deklarierte Variablen wird im globalen Bereich eine neue Variable erstellt, um eine Referenz zu erstellen. In einem Browser ist das globale Objekt window. Zum Beispiel:

function foo(arg) {
    bar = "some text";
}

ist äquivalent zu:

function foo(arg) {
    window.bar = "some text";
}

Wenn bar auf eine Variable im Bereich der foo-Funktion verweist, aber vergisst, sie mit var zu deklarieren, wird eine unerwartete globale Variable erstellt. In diesem Beispiel würde das Fehlen einer einfachen Zeichenfolge nicht viel Schaden anrichten, wäre aber auf jeden Fall schlecht.

Eine andere Möglichkeit, eine unerwartete globale Variable zu erstellen, besteht darin, diese zu verwenden:

function foo() {
    this.var1 = "potential accidental global";
}
// Foo自己调用,它指向全局对象(window),而不是未定义。
foo();
Sie können dies vermeiden, indem Sie am Anfang der JavaScript-Datei „use strict“ hinzufügen, wodurch ein strengerer JavaScript-Parsing-Modus aktiviert wird, um eine versehentliche Erstellung zu verhindern globale Variablen.

Obwohl es sich um unbekannte globale Variablen handelt, gibt es immer noch viele Codes, die mit expliziten globalen Variablen gefüllt sind. Per Definition sind diese nicht einsammelbar (es sei denn, sie sind als leer oder neu zugewiesen angegeben). Von besonderer Bedeutung sind globale Variablen, die zur vorübergehenden Speicherung und Verarbeitung großer Informationsmengen verwendet werden. Wenn Sie eine globale Variable zum Speichern einer großen Datenmenge verwenden müssen, stellen Sie sicher, dass Sie null angeben oder sie neu zuweisen, wenn Sie fertig sind.

2. Vergessene Timer und Rückrufe

Nehmen Sie setInterval als Beispiel, da es häufig in JavaScript verwendet wird.

var serverData = loadData();
setInterval(function() {
    var renderer = document.getElementById('renderer');
    if(renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }
}, 5000); //每五秒会执行一次

Der obige Codeausschnitt zeigt die Verwendung eines Timers, um auf Knoten oder Daten zu verweisen, die nicht mehr benötigt werden.

Das vom Renderer dargestellte Objekt wird möglicherweise irgendwann in der Zukunft gelöscht, wodurch ein ganzer Codeblock im internen Handler nicht mehr benötigt wird. Da der Timer jedoch noch aktiv ist, können der Handler und seine Abhängigkeiten nicht erfasst werden. Dies bedeutet, dass serverData, das große Datenmengen speichert, nicht erfasst werden kann.

Wenn Sie Beobachter verwenden, müssen Sie sicherstellen, dass Sie einen expliziten Aufruf zum Löschen dieser Beobachter tätigen, nachdem Sie sie nicht mehr verwendet haben (entweder wird der Beobachter nicht mehr benötigt oder das Objekt wird unzugänglich).

Als Entwickler müssen Sie sicherstellen, dass Sie sie explizit löschen, wenn Sie mit ihnen fertig sind (sonst ist der Zugriff auf das Objekt nicht mehr möglich).

In der Vergangenheit konnten einige Browser mit diesen Situationen nicht umgehen (der gute alte IE6). Glücklicherweise erledigen dies die meisten modernen Browser mittlerweile für Sie: Sie sammeln automatisch Beobachter-Handler, sobald auf das beobachtete Objekt nicht mehr zugegriffen werden kann, selbst wenn Sie vergessen, den Listener zu entfernen. Wir sollten diese Beobachter jedoch dennoch explizit entfernen, bevor das Objekt entsorgt wird. Zum Beispiel:

Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Heutige Browser (einschließlich IE und Edge) verwenden moderne Garbage-Collection-Algorithmen, die diese Zirkelverweise sofort erkennen und verarbeiten können. Mit anderen Worten: Es ist nicht erforderlich, „removeEventListener“ aufzurufen, bevor ein Knoten gelöscht wird.

Einige Frameworks oder Bibliotheken, wie z. B. JQuery, entfernen Listener automatisch, bevor Knoten entsorgt werden (bei Verwendung ihrer spezifischen API). Dies wird durch einen Mechanismus innerhalb der Bibliothek implementiert, der sicherstellt, dass keine Speicherlecks auftreten, selbst wenn es in problematischen Browsern wie IE 6 ausgeführt wird.

3. Abschlüsse

Abschlüsse sind ein Schlüsselaspekt der JavaScript-Entwicklung, bei der eine innere Funktion Variablen aus einer äußeren (eingeschlossenen) Funktion verwendet. Aufgrund der Details der Ausführung von JavaScript kann es auf folgende Weise zu Speicherverlusten kommen:

Einführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks

Dieser Code bewirkt eines: Jedes Mal, wenn replaceThing aufgerufen wird, theThing code > erhält ein neues Objekt, das ein großes Array und einen neuen Abschluss (someMethod) enthält. Gleichzeitig zeigt die Variable <code>unused auf einen Abschluss, der auf `originalThing verweist. replaceThing的时候,theThing都会得到一个包含一个大数组和一个新闭包(someMethod)的新对象。同时,变量unused指向一个引用了`originalThing的闭包。

是不是有点困惑了? 重要的是,一旦具有相同父作用域的多个闭包的作用域被创建,则这个作用域就可以被共享。

在这种情况下,为闭包someMethod而创建的作用域可以被unused共享的。unused内部存在一个对originalThing的引用。即使unused从未使用过,someMethod也可以在replaceThing的作用域之外(例如在全局范围内)通过theThing来被调用。

由于someMethod共享了unused闭包的作用域,那么unused引用包含的originalThing会迫使它保持活动状态(两个闭包之间的整个共享作用域)。这阻止了它被收集。

当这段代码重复运行时,可以观察到内存使用在稳定增长,当GC运行后,内存使用也不会变小。从本质上说,在运行过程中创建了一个闭包链表(它的根是以变量theThing

Verwirrt? Das Wichtigste ist, dass, sobald ein Bereich mit mehreren Abschlüssen mit demselben übergeordneten Bereich erstellt wurde, dieser Bereich gemeinsam genutzt werden kann.

In diesem Fall kann der für den Abschluss someMethod erstellte Bereich von unused gemeinsam genutzt werden. Es gibt einen internen Verweis auf originalThing innerhalb von unused. Auch wenn unused nie verwendet wird, kann someMethod außerhalb des Geltungsbereichs von replaceThing an theThing übergeben werden (z. B. im globalen Geltungsbereich). ) aufgerufen werden.

Da someMethod den Umfang des unused-Abschlusses teilt, gilt auch der Verweis von unused auf das enthaltene originalThing erzwinge, dass es am Leben bleibt (der gesamte gemeinsame Bereich zwischen den beiden Schließungen). Dies verhindert, dass es gesammelt wird.

Wenn dieser Code wiederholt ausgeführt wird, können Sie beobachten, dass die Speichernutzung stetig zunimmt. Wenn GC ausgeführt wird, wird die Speichernutzung nicht kleiner. Im Wesentlichen wird zur Laufzeit eine verknüpfte Liste von Abschlüssen erstellt (ihre Wurzel besteht aus der Variablen theThing), und der Bereich jedes Abschlusses verweist indirekt auf ein großes Array, was zu einem erheblichen Speicherverlust führte.

4. Referenzen vom DOM lösen

Manchmal kann es nützlich sein, DOM-Knoten in einer Datenstruktur zu speichern. Angenommen, Sie möchten schnell mehrere Zeilen in einer Tabelle aktualisieren, dann können Sie einen Verweis auf jede DOM-Zeile in einem Wörterbuch oder Array speichern. Auf diese Weise gibt es zwei Verweise auf dasselbe DOM-Element: einen im DOM-Baum und einen im Wörterbuch. Wenn Sie sich zu einem späteren Zeitpunkt dazu entschließen, diese Zeilen zu löschen, müssen Sie beide Referenzen unzugänglich machen. 🎜🎜Bei der Referenzierung interner Knoten oder Blattknoten im DOM-Baum ist noch ein weiteres Problem zu berücksichtigen. Wenn Sie in Ihrem Code einen Verweis auf eine Tabellenzelle (-Tag) beibehalten und beschließen, die Tabelle aus dem DOM zu entfernen und dabei einen Verweis auf diese bestimmte Zelle beizubehalten, kann es zu einem Speicherverlust kommen. 🎜🎜Man könnte meinen, dass der Müllsammler alles außer dieser Zelle freigibt. Dies ist jedoch nicht der Fall, da die Zelle ein untergeordneter Knoten der Tabelle ist und der untergeordnete Knoten einen Verweis auf den übergeordneten Knoten enthält. Dieser Verweis auf die Tabellenzelle behält die 🎜gesamte Tabelle im Speicher🎜 bei, sodass nach dem Entfernen von The Der referenzierte Knoten muss seine untergeordneten Knoten entfernen. 🎜

Das obige ist der detaillierte Inhalt vonEinführung in die JavaScript-Speicherverwaltung + Umgang mit vier häufigen Speicherlecks. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen