Ich habe den folgenden Chrome-Fehler protokolliert , der zu vielen schwerwiegenden und nicht offensichtlichen Speicherverlusten in meinem Code geführt hat:
(Diese Ergebnisse verwenden den Speicherprofiler von Chrome Dev Tools , mit dem der GC ausgeführt wird, und erstellen dann einen Heap-Snapshot von allem, was nicht gesammelt wurde.)
Im folgenden Code ist die someClass
Instanz Müll gesammelt (gut):
var someClass = function() {};
function f() {
var some = new someClass();
return function() {};
}
window.f_ = f();
In diesem Fall wird jedoch kein Müll gesammelt (schlecht):
var someClass = function() {};
function f() {
var some = new someClass();
function unreachable() { some; }
return function() {};
}
window.f_ = f();
Und der entsprechende Screenshot:
Es scheint, dass ein Abschluss (in diesem Fall function() {}
) alle Objekte "am Leben" hält, wenn das Objekt von einem anderen Abschluss im selben Kontext referenziert wird, unabhängig davon, ob dieser Abschluss selbst erreichbar ist oder nicht.
Meine Frage betrifft die Speicherbereinigung der Schließung in anderen Browsern (IE 9+ und Firefox). Ich bin mit den Tools des Webkits wie dem JavaScript-Heap-Profiler ziemlich vertraut, kenne aber nur wenige Tools anderer Browser, sodass ich dies nicht testen konnte.
In welchem dieser drei Fälle sammeln IE9 + und Firefox-Garbage die someClass
Instanz?
unreachable
Funktion nie ausgeführt, sodass tatsächlich nichts protokolliert wird.Antworten:
Soweit ich das beurteilen kann, handelt es sich nicht um einen Fehler, sondern um das erwartete Verhalten.
Auf der Speicherverwaltungsseite von Mozilla : "Ab 2012 liefern alle modernen Browser einen Mark-and-Sweep-Garbage-Collector aus." "Einschränkung: Objekte müssen explizit unerreichbar gemacht werden " .
In Ihren Beispielen, wo es fehlschlägt,
some
ist im Verschluss noch erreichbar. Ich habe zwei Möglichkeiten ausprobiert, um es unerreichbar zu machen, und beide funktionieren. Entweder Sie setzen,some=null
wenn Sie es nicht mehr brauchen, oder Sie setzenwindow.f_ = null;
und es wird weg sein.Aktualisieren
Ich habe es in Chrome 30, FF25, Opera 12 und IE10 unter Windows versucht.
Der Standard sagt nichts über die Speicherbereinigung aus, gibt aber einige Hinweise darauf, was passieren sollte.
Eine Funktion hat also Zugriff auf die Umgebung des übergeordneten Elements.
Sollte
some
also beim Schließen der Rückgabefunktion verfügbar sein.Warum ist es dann nicht immer verfügbar?
Es scheint, dass Chrome und FF klug genug sind, um die Variable in einigen Fällen zu entfernen, aber sowohl in Opera als auch in IE ist die
some
Variable im Closure verfügbar (Hinweis: Um dies anzuzeigen, setzen Sie einen Haltepunktreturn null
und überprüfen Sie den Debugger).Der GC könnte verbessert werden, um festzustellen, ob er
some
in den Funktionen verwendet wird oder nicht, aber er wird kompliziert.Ein schlechtes Beispiel:
Im obigen Beispiel kann der GC nicht feststellen, ob die Variable verwendet wird oder nicht (Code getestet und funktioniert in Chrome30, FF25, Opera 12 und IE10).
Der Speicher wird freigegeben, wenn der Verweis auf das Objekt durch Zuweisen eines anderen Werts unterbrochen wird
window.f_
.Meiner Meinung nach ist dies kein Fehler.
quelle
setTimeout()
Rückruf ausgeführt wird, ist dieser Funktionsumfang dessetTimeout()
Rückrufs abgeschlossen, und der gesamte Bereich sollte mit Müll gesammelt werden, wobei der Verweis auf freigegeben wirdsome
. Es kann kein Code mehr ausgeführt werden, der die Instanzsome
im Abschluss erreichen kann. Es sollte Müll gesammelt werden. Das letzte Beispiel ist noch schlimmer, weilunreachable()
es nicht einmal aufgerufen wird und niemand einen Hinweis darauf hat. Sein Geltungsbereich sollte ebenfalls überprüft werden. Diese beiden scheinen wie Fehler. In JS gibt es keine Sprachanforderung, um Dinge in einem Funktionsumfang "freizugeben".f()
aufgerufen wurde, gibt es keine tatsächlichen Verweisesome
mehr. Es ist nicht erreichbar und sollte GCed sein.eval
ist wirklich ein Sonderfall. Zum Beispieleval
kann nicht aliased (werden developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... ), zBvar eval2 = eval
. Wenneval
es verwendet wird (und da es nicht unter einem anderen Namen aufgerufen werden kann, ist dies einfach), müssen wir davon ausgehen, dass es alles im Umfang verwenden kann.Ich habe dies in IE9 + und Firefox getestet.
Live Seite hier .
Ich hoffte, mit einem Array von 500
function() {}
mit minimalem Speicherplatz fertig zu werden.Das war leider nicht der Fall. Jede leere Funktion enthält ein (für immer nicht erreichbares, aber nicht GC-fähiges) Array mit einer Million Zahlen.
Chrome wird schließlich angehalten und stirbt, Firefox beendet das Ganze, nachdem fast 4 GB RAM verwendet wurden, und der IE wächst asymptotisch langsamer, bis "Nicht genügend Speicher" angezeigt wird.
Durch Entfernen einer der kommentierten Zeilen wird alles behoben.
Es scheint, dass alle drei dieser Browser (Chrome, Firefox und IE) eine Umgebungsaufzeichnung pro Kontext und nicht pro Abschluss führen. Boris vermutet, dass der Grund für diese Entscheidung die Leistung ist, und das scheint wahrscheinlich, obwohl ich nicht sicher bin, wie performant sie angesichts des obigen Experiments genannt werden kann.
Wenn ein Verweis benötigt wird
some
(vorausgesetzt, ich habe ihn hier nicht verwendet, aber stellen Sie sich vor, ich hätte ihn verwendet), wenn stattich benutze
Es wird die Speicherprobleme beheben, indem der Abschluss in einen anderen Kontext als meine andere Funktion verschoben wird.
Dies wird mein Leben viel langweiliger machen.
PS Aus Neugier habe ich dies in Java versucht (mit seiner Fähigkeit, Klassen innerhalb von Funktionen zu definieren). GC funktioniert so, wie ich es mir ursprünglich erhofft hatte.
quelle
Die Heuristiken variieren, aber eine übliche Methode zur Implementierung dieser Art besteht darin, für jeden Anruf
f()
in Ihrem Fall einen Umgebungsdatensatz zu erstellen und nur die Einheimischen zu speichernf
, die tatsächlich (durch einen Abschluss) in diesem Umgebungsdatensatz geschlossen sind. Dann jede Schließung, die im Aufruf erstellt wurde, umf
den Umgebungsdatensatz am Leben zu erhalten. Ich glaube, so implementiert Firefox zumindest Verschlüsse.Dies hat den Vorteil eines schnellen Zugriffs auf geschlossene Variablen und einer einfachen Implementierung. Es hat den Nachteil des beobachteten Effekts, dass ein kurzlebiger Verschluss, der über eine Variable schließt, dazu führt, dass er durch langlebige Verschlüsse am Leben erhalten wird.
Man könnte versuchen, mehrere Umgebungsdatensätze für verschiedene Schließungen zu erstellen, je nachdem, was sie tatsächlich schließen, aber das kann sehr schnell sehr kompliziert werden und selbst Leistungs- und Speicherprobleme verursachen ...
quelle
O(n^2)
oderO(2^n)
als eine Explosion, aber nicht eine proportionale Zunahme.wie add (5); // gibt 5 zurück
addiere (20); // gibt 25 (5 + 20) zurück
addiere (3); // gibt 28 (25 + 3) zurück
Zwei Möglichkeiten, wie Sie dies zuerst tun können, sind normal. Definieren Sie eine globale Variable. Natürlich können Sie eine globale Variable verwenden, um die Summe zu speichern. Aber denken Sie daran, dass dieser Typ Sie lebendig essen wird, wenn Sie (ab) Globals verwenden.
Jetzt neueste Methode mit Closure ohne definierte globale Variable
quelle
quelle
quelle