Speicherlecks in JavaScript: wie man sie beseitigt (2/2)

Dieser Artikel ist die Fortsetzung des Artikels „Die 4 Arten von Speicherlecks“
c

Das unintuitive Verhalten von Garbage Collectors

Obwohl Garbage Collectors sehr praktisch sind, bringen sie ihren Anteil an Kompromissen mit sich. Einer dieser Kompromisse ist ihr Nichtdeterminismus. Mit anderen Worten, Garbage Collectors sind unberechenbar. Wann eine Garbage Collection durchgeführt wird, ist in der Regel nicht mit Sicherheit feststellbar. Dies bedeutet, dass das Programm in einigen Fällen mehr Speicher als nötig verwendet. In anderen Fällen kann es bei sensiblen Anwendungen zu kurzen Unterbrechungen kommen. Obwohl nicht deterministisch bedeutet, dass man nie sicher sein kann, wann die Sammlung durchgeführt wird, haben die meisten GC-Implementierungen ein gemeinsames Muster der Durchführung der Sammlung während der Zuweisung. Wenn keine Zuordnung erfolgt, bleiben die meisten GS im Leerlauf.
Stellen Sie sich das folgende Szenario vor:

  1. Es wird ein konsequenter Satz von Zuweisungen vorgenommen
  2. Die meisten (oder alle) dieser Elemente sind als unzugänglich markiert (stellen Sie sich vor, Sie nullen eine Referenz, die auf einen Cache zeigt, der nicht mehr benötigt wird).
  3. Es werden keine Zulagen mehr gemacht

In diesem Szenario werden die meisten GCs nicht mehr erfasst. Das heißt, selbst wenn unzugängliche Gegenstände zur Abholung verfügbar sind, werden sie vom Sammler nicht beansprucht. Dies sind an sich keine Lecks, aber das Ergebnis ist höher als die normale Speichernutzung.
Google bietet ein großartiges Beispiel für dieses Verhalten in seinen JavaScript Memory Profiling-Dokumenten, Beispiel #2.

Wir stellen die Tools zur Speicherprofilerstellung von Chrome vor

Chrome bietet eine gute Reihe von Tools zur Diagnose der Speichernutzung von JavaScript-Code. Es gibt hauptsächlich zwei Ansichten, die sich auf den Speicher beziehen: die Timeline-Ansicht und die Profilansicht.

Timeline-Ansicht

Die Timeline-Ansicht ist unerlässlich, um ungewöhnliche Speichermuster in unserem Code zu entdecken. Wenn wir nach einem großen Leck suchen, sollten Sie die periodischen Sprünge, die nicht so stark abnehmen, wie sie nach der Sammlung gewachsen sind, alarmieren. In dieser Aufnahme sehen wir, wie das stetige Wachstum eines undichten Objekts aussieht. Auch nach der abschließenden großen Sammlung ist der verbrauchte Gesamtspeicher am Ende größer als am Anfang. Die Anzahl der Knoten auch. So viele Hinweise auf DOM-Lecks irgendwo im Code.

Profilansicht

Es ist diese Ansicht, mit der Sie viel Zeit verbringen werden. Die Profilansicht ermöglicht es Ihnen, einen Schnappschuss zu erhalten und Schnappschüsse der Speichernutzung Ihres JavaScript-Codes zu vergleichen.
Auf diese Weise können Sie Zuordnungen im Laufe der Zeit speichern. In allen Ergebnisansichten gibt es verschiedene Arten von Listen, aber die relevantesten für unsere Aufgabe sind die Übersichtsliste und die Vergleichsliste.
Die Zusammenfassungsansicht gibt uns einen Überblick über die verschiedenen Arten von zugewiesenen Objekten und ihre aggregierte Größe: flache Größe (die Summe aller Objekte eines bestimmten Typs) und beibehaltene Größe (die flache Größe plus die Größe anderer Objekte, die aufgrund dieses Objekts beibehalten werden ). Dies gibt auch eine Vorstellung von der Entfernung dieses Objekts von seinem Wurzel-GC (der Entfernung).
Die Vergleichsliste gibt uns die gleichen Informationen, ermöglicht uns aber, die verschiedenen Schnappschüsse zu vergleichen. Dies ist besonders nützlich, um Lecks zu finden.
Beispiel: Lecks finden mit Chrome
Es gibt hauptsächlich zwei Arten von Lecks: Lecks, die eine periodische Erhöhung der Speichernutzung verursachen, und Lecks, die einmalig auftreten und keine weitere Erhöhung der Speichernutzung verursachen. Aus offensichtlichen Gründen ist es einfacher, Lecks zu finden, wenn sie periodisch auftreten. Sie sind auch die problematischsten, wenn der Speicher im Laufe der Zeit zunimmt, können Lecks dieser Art den Browser verlangsamen oder dazu führen, dass das Skript stoppt. Nicht periodische Lecks können leicht gefunden werden, wenn sie groß genug sind, um unter anderen Zuordnungen bemerkt zu werden. Dies ist normalerweise nicht der Fall, sodass sie unbemerkt bleiben. In gewisser Weise könnten Lecks, die nur einmal auftreten, als Optimierungsprobleme kategorisiert werden. Regelmäßige Leaks sind jedoch Fehler und sollten behoben werden.
Zur Veranschaulichung nehmen wir ein Beispiel im Chrome-Dokument. Der Code wird unten kopiert:
var x = [];
function createSomeNodes() {
var div,
i = 100,
frag = document.createDocumentFragment();
for (;i > 0; i--) {
div = document.createElement("div");
div.appendChild(document.createTextNode(i + " - "+ new Date().toTimeString()));
frag.appendChild(div);
}
document.getElementById("nodes").appendChild(frag);
}
function grow() {
x.push(new Array(1000000).join('x'));
createSomeNodes();
setTimeout(grow,1000);
}
Wenn grow aufgerufen wird, erstellt es zunächst DIV-Knoten und fügt sie an das DOM an. Es wird auch ein großes Array zuweisen und ein Array hinzufügen, auf das von einer globalen Variablen verwiesen wird. Dies führt zu einem stetigen Anstieg des Speichers, der mit den oben genannten Tools gefunden werden kann.
Wir beobachten im Allgemeinen ein oszillierendes Speichernutzungsmuster in Garbage-Collection-basierten Sprachen. Dies wird erwartet, wenn der Code in einer Schleife ausgeführt wird, die die Zuordnungen erzeugt, was normalerweise der Fall ist. Wir werden nach regelmäßigen Gedächtnisschubs suchen, die nach dem Sammeln nicht auf vorherige Werte zurückfallen.
Prüfen Sie zunächst, ob der Speicher periodisch zunimmt.
Dafür eignet sich die View Timeline hervorragend. Öffnen Sie das Beispiel in Chrome, öffnen Sie die Dev Tools, gehen Sie zur Timeline, wählen Sie Speicher aus und klicken Sie auf die Aufnahmeschaltfläche. Gehen Sie dann auf die Seite und klicken Sie auf die Schaltfläche, um das Speicherleck zu starten. Warten Sie etwas, dann stoppen Sie die Aufnahme und sehen Sie sich das Ergebnis an:

Speicherleck in der Timeline-Ansicht

Dieses Beispiel wird weiterhin jede Sekunde Speicher lecken. Fügen Sie nach dem Beenden der Aufzeichnung einen Haltepunkt in der Grow-Funktion hinzu, um zu verhindern, dass das Skript Chrome zum Schließen der Seite zwingt. Es gibt 2 große Hinweise in diesem Bild, die zeigen, dass wir Speicherlecks haben. Die Diagramme von Knoten (grüne Linie) und JS-Heap (blaue Linie). Die Knoten nehmen stetig zu und kommen nie herunter. Das ist eine große rote Fahne.
Der JS Heap zeigt auch einen stetigen Anstieg der Speichernutzung. Aufgrund der Auswirkungen des Garbage Collectors ist es schwieriger zu sehen. Sie können ein Muster von anfänglichem Speicherwachstum beobachten, gefolgt von einer starken Reduzierung, gefolgt von Wachstum, dann einer Spitze, gefolgt von einer weiteren Reduzierung. Der Schlüssel in diesem Fall ist, dass die Heap-Größe nach jeder Reduzierung der Speichernutzung größer bleibt als beim vorherigen Mal. Das bedeutet, dass der Garbage Collector zwar erfolgreich viel Speicher zurückfordert, aber regelmäßig etwas verloren geht.
Wir sind uns jetzt sicher, dass es ein Leck gibt. Lass es uns finden.
d

Machen Sie zwei Schnappschüsse

Um das Leck zu finden, gehen wir jetzt zum Profilbereich von Chrome Dev Tools. Um die Speicherauslastung auf einem überschaubaren Niveau zu halten, laden Sie die Seite vor diesem Schritt neu. Wir werden die Take Heap Snapshot-Funktion verwenden.
Laden Sie die Seite neu und machen Sie direkt nach dem Laden einen Heap-Snapshot. Wir werden diesen Schnappschuss als Referenz verwenden. Klicken Sie nun erneut auf die Schaltfläche, warten Sie einige Sekunden und machen Sie einen neuen Schnappschuss. Nachdem Sie den Snapshot erstellt haben, ist es eine gute Idee, einen Haltepunkt in das Skript einzufügen, um zu verhindern, dass das Leck mehr Speicher verbraucht.

Heap-Snapshots

Es gibt zwei Möglichkeiten, Zuordnungen zwischen zwei Snapshots zu betrachten. Entweder Sie klicken auf Zusammenfassung und gehen dann nach rechts und wählen zwischen Snapshot 1 und Snapshot 2 zugeordnete Objekte aus, oder Sie klicken statt Zusammenfassung auf Vergleich. In beiden Fällen sehen wir eine Liste von Objekten, die zwischen den beiden Snapshots zugewiesen wurden.
In diesem Fall ist es ziemlich einfach, die Lecks zu finden: Sie sind groß. Sehen Sie sich das Größendelta des (Zeichenfolgen-)Konstruktors an. 8 MB für 58 neue Artikel. Das sieht verdächtig aus: Neue Objekte werden zugewiesen, aber nicht freigegeben, und 8 MB werden verbraucht.
Wenn wir die Liste der Zuweisungen für den (String-)Konstruktor öffnen, werden wir feststellen, dass es unter vielen kleinen einige wenige große Zuweisungen gibt. Die Großen ziehen sofort unsere Aufmerksamkeit auf sich. Wenn wir eine davon auswählen, finden wir etwas Interessantes damit im Abschnitt Retainer direkt darunter.

Retainer für das ausgewählte Objekt

Wir sehen, dass die ausgewählte Zuordnung Teil eines Arrays ist. Das Array wiederum wird von der Variablen x im globalen Fensterobjekt referenziert. Dies gibt uns den vollständigen Pfad von unserem großen Objekt zu seinem nicht sammelbaren Stamm (Fenster). Wir haben unser potenzielles Leck gefunden und wo darauf verwiesen wird.
So weit, ist es gut. Aber unser Beispiel war einfach: Große Allokationen wie im Beispiel sind nicht die Norm. Glücklicherweise verliert unser Beispiel auch DOM-Knoten, die kleiner sind. Es ist einfach, diese Knoten mit dem obigen Schnappschuss zu finden, aber bei größeren Sites werden die Dinge komplizierter. Neuere Versionen von Chrome bieten ein zusätzliches Tool, das für unsere Arbeit gut geeignet ist: die Funktion Record Heap Allocations.

Speichern Sie die Heap-Zuordnung, um Lecks zu finden

Deaktivieren Sie den zuvor festgelegten Haltepunkt, lassen Sie das Skript ausführen und kehren Sie zum Profilabschnitt der Chrome-Entwicklungstools zurück. Tippen Sie nun auf Record Heap Allocations. Während das Tool ausgeführt wird, sehen Sie im oberen Diagramm blaue Spitzen. es stellt Zulagen dar. Jede Sekunde wird durch den Code eine große Zuweisung erzeugt. Lassen Sie es einige Sekunden lang laufen und stoppen Sie es dann (vergessen Sie nicht, einen Haltepunkt hinzuzufügen, um zu verhindern, dass Chrome mehr Speicher verbraucht).
Heap gespeicherte Zertifikate.
In diesem Bild sehen Sie die Killerfunktion dieses Tools: Wählen Sie einen Teil der Zeitleiste aus, um zu sehen, welche Zuweisungen während dieser Zeit vorgenommen wurden. Wir definieren die Auswahl, die den großen Spitzen am nächsten liegt. In der Liste erscheinen nur drei Konstruktoren. Einer von ihnen bezieht sich auf unser großes Leck ((string)), der nächste bezieht sich auf DOM-Zuweisungen und der letzte ist der Text-Konstruktor (der Konstruktor für DOM-Blattknoten, die Text enthalten).
Wählen Sie einen der HTMLDivElement-Konstruktoren aus der Liste aus und klicken Sie dann auf Zuweisungsstapel auswählen.
Ausgewähltes Element in den Ergebnissen der Heap-Zuordnung
BAMM! Wir wissen jetzt, wo dieses Element zugewiesen wurde (grow -> createSomeNodes). Wenn wir uns jede Spitze im Diagramm genau ansehen, werden wir feststellen, dass der HTMLDivElement-Konstruktor viel aufgerufen wird. Wenn wir zur Snapshot-Vergleichsansicht zurückkehren, werden wir feststellen, dass dieser Konstruktor viel Zuweisung, aber keine Löschung anzeigt. Mit anderen Worten, es weist regelmäßig Speicher zu, ohne dass der GC diesen zurückfordern kann. All dies sind Symptome eines Lecks, und außerdem wissen wir genau, wo diese Objekte zugeordnet sind (die Funktion createSomeNodes). Jetzt ist es an der Zeit, zum Code zurückzukehren, ihn zu studieren und die Lecks zu beheben.

  • Eine weitere nützliche Funktion:

In der Ergebnisansicht der Heap-Zuweisungen können wir die Zuweisungsansicht anstelle der Zusammenfassung auswählen.
Diese Ansicht gibt uns eine Liste von Funktionen und den zugehörigen Speicherzuweisungen. Wir können sofort wachsen sehen und createSomeNodes hervorheben. Wenn wir grow auswählen, werfen wir einen Blick auf die Konstruktoren der zugehörigen Objekte, die davon aufgerufen werden. Wir bemerken (string), HTMLDivElement und Text, von denen wir bereits wissen, dass sie die Konstruktoren der undichten Objekte sind.
Die Kombination dieser Tools kann einen großen Beitrag zum Auffinden von Lecks leisten. Spielen Sie damit, führen Sie verschiedene Profilerstellungen in Ihren Produktionsstätten durch (idealerweise Code, nicht minimiert oder verschleiert). Sehen Sie nach, ob Sie undichte Stellen oder Gegenstände finden können, die länger aufbewahrt werden, als sie sollten (Hinweis: Sie sind schwerer zu finden).
Um diese Funktionen zu verwenden, gehen Sie zu Dev Tools -> Settings und aktivieren Sie „Stack-Traces der Heap-Zuordnung aufzeichnen“. Dies ist vor der Aufnahme erforderlich.

  • Um weiter zu gehen:

Speicherverwaltung – ​​Mozilla Developer Network
JScript-Speicherlecks – Douglas Crockford (alt, in Bezug auf Internet Explorer 6-Lecks)
JavaScript-Speicherprofilerstellung – Chrome Developer Docs
Speicherdiagnose – Google Developers
Eine interessante Art von JavaScript-Speicherleck – Meteor-Blog
Grokking V8-Verschlüsse
Fazit
Speicherlecks können und werden in Garbage Collection-Sprachen wie JavaScript auftreten. Sie können eine Weile im Versteck leben und schließlich Chaos anrichten. Aus diesem Grund sind Tools zur Erstellung von Speicherprofilen unerlässlich, um Speicherlecks zu finden. Profiling sollte Teil der Entwicklungszyklen sein, insbesondere für mittlere bis große Anwendungen. Beginnen Sie jetzt, um Ihren Benutzern das bestmögliche Erlebnis zu bieten.
Gut debuggen!
Finden Sie den ersten Artikel zu diesem Thema hier !
[separator type=““ size=““ icon=“star“] [actionbox color=“default“ title=““ description=“JS-REPUBLIC ist ein auf JavaScript-Entwicklung spezialisiertes Dienstleistungsunternehmen. Wir sind ein anerkanntes Ausbildungszentrum. Alle unsere technischen Schulungen finden Sie auf unserer Partnerseite für Schulungen“ btn_label=“Unser Training“ btn_link=“http://training.ux-republic.com“ btn_color=“primary“ btn_size=“big“ btn_icon=“star“ btn_external ="1"]