Schwierige Speicherlecks aufgrund von getrennten Fenstern finden und beheben
Was ist ein Speicherleck in JavaScript?
Ein Speicherleck ist eine unbeabsichtigte Erhöhung der von einer Anwendung verwendeten Arbeitsspeichermenge im Laufe der Zeit. In JavaScript treten Speicherlecks auf, wenn Objekte nicht mehr benötigt werden, aber weiterhin von Funktionen oder anderen Objekten referenziert werden. Diese Verweise verhindern, dass die nicht benötigten Objekte vom Garbage Collector wiederverwendet werden.
Die Aufgabe des Garbage Collectors besteht darin, Objekte zu identifizieren und wiederzuverwenden, die von der Anwendung nicht mehr erreichbar sind. Das funktioniert auch dann, wenn Objekte auf sich selbst verweisen oder sich zyklisch gegenseitig referenzieren. Sobald keine Verweise mehr vorhanden sind, über die eine Anwendung auf eine Gruppe von Objekten zugreifen könnte, kann die Garbage Collection ausgeführt werden.
let A = {};
console.log(A); // local variable reference
let B = {A}; // B.A is a second reference to A
A = null; // unset local variable reference
console.log(B.A); // A can still be referenced by B
B.A = null; // unset B's reference to A
// No references to A are left. It can be garbage collected.
Eine besonders heikle Art von Speicherleck tritt auf, wenn eine Anwendung auf Objekte verweist, die einen eigenen Lebenszyklus haben, z. B. DOM-Elemente oder Pop-up-Fenster. Es ist möglich, dass diese Arten von Objekten ohne Wissen der Anwendung nicht mehr verwendet werden. Das bedeutet, dass der Anwendungscode möglicherweise die einzigen verbleibenden Verweise auf ein Objekt enthält, das andernfalls durch die Garbage Collection gelöscht werden könnte.
Was ist ein separates Fenster?
Im folgenden Beispiel enthält eine Anwendung zum Ansehen von Präsentationen Schaltflächen zum Öffnen und Schließen eines Pop-ups für die Notizen des Vortragenden. Angenommen, ein Nutzer klickt auf Notizen anzeigen und schließt dann das Pop-up-Fenster direkt, anstatt auf die Schaltfläche Notizen ausblenden zu klicken. Die Variable notesWindow enthält dann weiterhin einen Verweis auf das Pop-up, auf das zugegriffen werden kann, obwohl es nicht mehr verwendet wird.
<button id="show">Show Notes</button>
<button id="hide">Hide Notes</button>
<script type="module">
let notesWindow;
document.getElementById('show').onclick = () => {
notesWindow = window.open('/presenter-notes.html');
};
document.getElementById('hide').onclick = () => {
if (notesWindow) notesWindow.close();
};
</script>
Dies ist ein Beispiel für ein separates Fenster. Das Pop-up-Fenster wurde geschlossen, aber unser Code enthält eine Referenz darauf, die verhindert, dass der Browser es löschen und den Arbeitsspeicher zurückfordern kann.
Wenn eine Seite window.open() aufruft, um ein neues Browserfenster oder einen neuen Tab zu erstellen, wird ein Window-Objekt zurückgegeben, das das Fenster oder den Tab darstellt. Auch nachdem ein solches Fenster geschlossen wurde oder der Nutzer es verlassen hat, kann das von window.open() zurückgegebene Window-Objekt weiterhin verwendet werden, um auf Informationen zuzugreifen. Dies ist eine Art von getrenntem Fenster: Da JavaScript-Code weiterhin potenziell auf Eigenschaften des geschlossenen Window-Objekts zugreifen kann, muss es im Arbeitsspeicher gehalten werden. Wenn das Fenster viele JavaScript-Objekte oder Iframes enthielt, kann dieser Arbeitsspeicher erst wieder freigegeben werden, wenn keine JavaScript-Referenzen mehr auf die Eigenschaften des Fensters vorhanden sind.
Dasselbe Problem kann auch bei der Verwendung von <iframe>-Elementen auftreten. Iframes verhalten sich wie verschachtelte Fenster, die Dokumente enthalten. Die contentWindow-Property bietet Zugriff auf das enthaltene Window-Objekt, ähnlich wie der von window.open() zurückgegebene Wert. JavaScript-Code kann eine Referenz auf die contentWindow oder contentDocument eines Iframes beibehalten, auch wenn der Iframe aus dem DOM entfernt wird oder seine URL geändert wird. Dadurch wird verhindert, dass das Dokument durch die Garbage Collection entfernt wird, da weiterhin auf seine Eigenschaften zugegriffen werden kann.
Wenn eine Referenz auf die document in einem Fenster oder iFrame aus JavaScript beibehalten wird, wird dieses Dokument im Arbeitsspeicher gehalten, auch wenn das enthaltene Fenster oder der iFrame zu einer neuen URL weitergeleitet wird. Das kann besonders problematisch sein, wenn das JavaScript, das diese Referenz enthält, nicht erkennt, dass das Fenster/der Frame zu einer neuen URL weitergeleitet wurde, da nicht bekannt ist, wann es sich um die letzte Referenz handelt, die ein Dokument im Arbeitsspeicher hält.
Wie getrennte Fenster zu Speicherlecks führen
Wenn Sie mit Fenstern und Iframes in derselben Domain wie die Hauptseite arbeiten, ist es üblich, auf Ereignisse zu warten oder über Dokumentgrenzen hinweg auf Properties zuzugreifen. Sehen wir uns beispielsweise noch einmal eine Variante des Beispiels für die Präsentationsanzeige vom Anfang dieses Leitfadens an. Der Betrachter öffnet ein zweites Fenster, um die Vortragsnotizen anzuzeigen. Im Fenster für die Vortragsnotizen wird auf click-Ereignisse gewartet, um zur nächsten Folie zu wechseln. Wenn der Nutzer dieses Notizenfenster schließt, hat das im ursprünglichen übergeordneten Fenster ausgeführte JavaScript weiterhin vollen Zugriff auf das Dokument mit den Sprechernotizen:
<button id="notes">Show Presenter Notes</button>
<script type="module">
let notesWindow;
function showNotes() {
notesWindow = window.open('/presenter-notes.html');
notesWindow.document.addEventListener('click', nextSlide);
}
document.getElementById('notes').onclick = showNotes;
let slide = 1;
function nextSlide() {
slide += 1;
notesWindow.document.title = `Slide ${slide}`;
}
document.body.onclick = nextSlide;
</script>
Angenommen, wir schließen das Browserfenster, das oben von showNotes() erstellt wurde. Es gibt keinen Ereignishandler, der erkennt, dass das Fenster geschlossen wurde. Daher wird unser Code nicht darüber informiert, dass er alle Verweise auf das Dokument bereinigen soll. Die Funktion nextSlide() ist weiterhin „aktiv“, da sie auf der Hauptseite als Klick-Handler gebunden ist. Da nextSlide einen Verweis auf notesWindow enthält, wird weiterhin auf das Fenster verwiesen und es kann nicht durch die Garbage Collection gelöscht werden.
Es gibt eine Reihe weiterer Szenarien, in denen Verweise versehentlich beibehalten werden, sodass getrennte Fenster nicht für die Garbage Collection infrage kommen:
Ereignishandler können im ursprünglichen Dokument eines Iframes registriert werden, bevor der Frame zur gewünschten URL weitergeleitet wird. Dies führt zu versehentlichen Verweis auf das Dokument und den Iframe, die nach dem Bereinigen anderer Verweise bestehen bleiben.
Ein speicherintensives Dokument, das in einem Fenster oder IFrame geladen wird, kann versehentlich im Arbeitsspeicher gehalten werden, auch wenn Sie zu einer neuen URL weitergeleitet wurden. Das liegt oft daran, dass die übergeordnete Seite Verweise auf das Dokument beibehält, um das Entfernen von Listenern zu ermöglichen.
Wenn ein JavaScript-Objekt an ein anderes Fenster oder einen anderen IFrame übergeben wird, enthält die Prototypkette des Objekts Verweise auf die Umgebung, in der es erstellt wurde, einschließlich des Fensters, in dem es erstellt wurde. Es ist also genauso wichtig, Verweise auf Objekte aus anderen Fenstern wie auf die Fenster selbst zu vermeiden.
index.html:
<script> let currentFiles; function load(files) { // this retains the popup: currentFiles = files; } window.open('upload.html'); </script>upload.html:
<input type="file" id="file" /> <script> file.onchange = () => { parent.load(file.files); }; </script>
Speicherlecks durch getrennte Fenster erkennen
Das Aufspüren von Speicherlecks kann schwierig sein. Es ist oft schwierig, diese Probleme isoliert zu reproduzieren, insbesondere wenn mehrere Dokumente oder Fenster beteiligt sind. Das Ganze wird noch komplizierter, da die Prüfung potenzieller geleakter Referenzen zusätzliche Referenzen erstellen kann, die verhindern, dass die geprüften Objekte vom Garbage Collector erfasst werden. Zu diesem Zweck ist es sinnvoll, mit Tools zu beginnen, die diese Möglichkeit ausdrücklich nicht bieten.
Ein guter Ausgangspunkt für die Behebung von Speicherproblemen ist ein Heap-Snapshot. Dies bietet einen aktuellen Überblick über den Arbeitsspeicher, der derzeit von einer Anwendung verwendet wird – also alle Objekte, die erstellt, aber noch nicht durch die Garbage Collection gelöscht wurden. Heap-Snapshots enthalten nützliche Informationen zu Objekten, einschließlich ihrer Größe und einer Liste der Variablen und Schließungen, die auf sie verweisen.