it-swarm.com.de

Wie JavaScript-Verschlüsse Müll gesammelt werden

Ich habe den folgenden Chrome-Fehler protokolliert, der zu vielen gravierenden und nicht offensichtlichen Speicherverlusten in meinem Code geführt hat:

(Diese Ergebnisse verwenden den memory profiler von Chrome Dev Tools.), Der den GC ausführt und anschließend einen Heap-Snapshot von allem sammelt, das nicht in einem Müllkorb gesammelt wurde.

Im folgenden Code ist die someClass-Instanz Müllsammlung (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 den entsprechenden Screenshot:

screenshot of Chromebug

Es scheint, dass eine Schließung (in diesem Fall function() {}) alle Objekte "lebendig" hält, wenn das Objekt von einer anderen Schließung im selben Kontext referenziert wird, unabhängig davon, ob diese Schließung 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 von webkit, wie dem JavaScript-Heap-Profiler, ziemlich vertraut, aber ich kenne wenig von den Tools anderer Browser, daher konnte ich das nicht testen.

In welchem ​​dieser drei Fälle werden IE9 + - und Firefox-Abfälle die InstanzsomeClasssammeln?

165
Paul Draper

Ich habe dies in IE9 + und Firefox getestet.

function f() {
  var some = [];
  while(some.length < 1e6) {
    some.Push(some.length);
  }
  function g() { some; } //removing this fixes a massive memory leak
  return function() {};   //or removing this
}

var a = [];
var interval = setInterval(function() {
  var len = a.Push(f());
  if(len >= 500) {
    clearInterval(interval);
  }
}, 10);

Live Site hier .

Ich hatte gehofft, mit einem Array von 500 function() {} zu arbeiten und dabei nur wenig Speicher zu verwenden.

Das war leider nicht der Fall. Jede leere Funktion hält an einem (für immer nicht erreichbaren, aber nicht GC-basierten) Array von einer Million Zahlen.

Chrome stoppt und stirbt schließlich, Firefox beendet das Ganze, nachdem er fast 4 GB RAM verwendet hat, und IE wird asymptotisch langsamer, bis "Out of memory" angezeigt wird.

Das Entfernen einer der kommentierten Zeilen behebt alles.

Es scheint, dass alle drei dieser Browser (Chrome, Firefox und IE) einen Umgebungsdatensatz pro Kontext und nicht pro Schließung aufzeichnen. Boris vermutet, dass der Grund für diese Entscheidung die Leistung ist, und das ist wahrscheinlich, obwohl ich nicht sicher bin, wie performant sie angesichts des oben genannten Experiments sein kann.

Wenn eine Schließung erforderlich ist, die auf some verweist (natürlich habe ich sie hier nicht verwendet, aber stellen Sie sich vor, ich hätte es getan), wenn statt

function g() { some; }

Ich benutze

var g = (function(some) { return function() { some; }; )(some);

es wird die Speicherprobleme beheben, indem der Verschluss in einen anderen Kontext verschoben wird als meine andere Funktion.

Dies wird mein Leben viel langweiliger machen.

P.S. Aus Neugierde habe ich dies in Java ausprobiert (mit seiner Fähigkeit, Klassen innerhalb von Funktionen zu definieren). GC funktioniert so, wie ich es ursprünglich für Javascript erhofft hatte.

48
Paul Draper

Soweit ich das beurteilen kann, handelt es sich nicht um einen Bug, sondern um das erwartete Verhalten.

Aus Mozillas Speicherverwaltungsseite : "Ab 2012 liefern alle modernen Browser einen Mark-and-Sweep-Garbage-Collector." "Einschränkung: Objekte müssen explizit unerreichbar gemacht werden " .

In Ihren Beispielen, in denen dies fehlschlägt, ist some im Closure 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 setzen window.f_ = null;, und es wird weg sein.

Update

Ich habe es in Chrome 30, FF25, Opera 12 und IE10 unter Windows versucht.

Das Standard sagt nichts über die Garbage Collection aus, gibt aber Hinweise darauf, was passieren soll.

  • Abschnitt 13 Funktionsdefinition, Schritt 4: "Das Schließen sei das Ergebnis der Erstellung eines neuen Funktionsobjekts, wie in 13.2 angegeben."
  • Abschnitt 13.2 "Eine von Scope festgelegte lexikalische Umgebung" (scope = closure)
  • Abschnitt 10.2 Lexikalische Umgebungen:

"Die äußere Referenz einer (inneren) lexikalischen Umgebung ist eine Referenz auf die lexikalische Umgebung, die die innere lexikalische Umgebung logisch umgibt.

Eine äußere lexikalische Umgebung kann natürlich eine eigene äußere lexikalische Umgebung haben. Eine lexikalische Umgebung kann als äußere Umgebung für mehrere innere lexikalische Umgebungen dienen. Wenn beispielsweise eine Funktionsdeklaration zwei verschachtelte Funktionsdeklarationen enthält, haben die Lexikalischen Umgebungen jeder der verschachtelten Funktionen ihre äußere lexikalische Umgebung die lexikalische Umgebung der aktuellen Ausführung der umgebenden Funktion. "

Eine Funktion hat also Zugriff auf die Umgebung des übergeordneten Elements.

Daher sollte some beim Schließen der Rückgabefunktion verfügbar sein.

Warum ist es dann nicht immer verfügbar?

Es scheint, dass Chrome und FF in einigen Fällen intelligent genug sind, um die Variable zu entfernen, aber sowohl in Opera als auch in IE ist die Variable some verfügbar im Closure (NB: um dies zu sehen, setze einen Breakpoint auf return null und überprüfe den Debugger).

Der GC könnte verbessert werden, um festzustellen, ob some in den Funktionen verwendet wird oder nicht, dies ist jedoch kompliziert.

Ein schlechtes Beispiel:

var someClass = function() {};

function f() {
  var some = new someClass();
  return function(code) {
    console.log(eval(code));
  };
}

window.f_ = f();
window.f_('some');

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 unterbrochen wird, indem window.f_ ein anderer Wert zugewiesen wird.

Meiner Meinung nach ist dies kein Fehler.

77
some

Die Heuristiken variieren, aber eine gängige Methode zum Implementieren dieser Art ist das Erstellen eines Umgebungsdatensatzes für jeden Aufruf von f() in Ihrem Fall und das Speichern der Ortsnamen von f, die tatsächlich geschlossen sind (durch einige Schließung) diese Umgebung aufnehmen. Dann behält jede Schließung, die in dem Aufruf von f erstellt wird, den Umgebungsdatensatz aufrecht. Ich glaube, dass Firefox so zumindest Schließungen implementiert.

Dies hat den Vorteil eines schnellen Zugriffs auf geschlossene Variablen und eine einfache Implementierung. Sie hat den Nachteil des beobachteten Effekts, bei dem eine kurzlebige Schließung einiger Variablen dazu führt, dass sie durch langlebige Schließungen 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. Dies kann jedoch sehr schnell sehr kompliziert werden und zu eigenen Leistungs- und Speicherproblemen führen.

15
Boris Zbarsky

(function(){

   function addFn(){

    var total = 0;
	
	if(total==0){	
	return function(val){
      total += val;	 
      console.log("hello:"+total);
	   return total+9;
    }	
	}else{
	 console.log("hey:"+total);
	}
	 
  };

   var add = addFn();
   console.log(add);  
   

    var r= add(5);  //5
	console.log("r:"+r); //14 
	var r= add(20);  //25
	console.log("r:"+r); //34
	var r= add(10);  //35
	console.log("r:"+r);  //44
	
	
var addB = addFn();
	 var r= addB(6);  //6
	 var r= addB(4);  //10
	  var r= addB(19);  //29
    
  
}());

0
Avinash Maurya
  1. Behalten Sie den Status zwischen Funktionsaufrufen bei Nehmen wir an, Sie haben die Funktion add (), und Sie möchten, dass alle Werte, die in mehreren Aufrufen an sie übergeben werden, hinzugefügt werden und die Summe zurückgegeben wird. 

wie add (5); // gibt 5 zurück

addiere (20); // gibt 25 (5 + 20) zurück

addiere (3); // gibt 28 (25 + 3) zurück

sie können dies zuerst tun. Normalerweise 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 Kerl Sie lebendig frisst, wenn Sie (ab) Globals verwenden.

jetzt der letzte Weg mit Schließung ohne Definition globale Variable

(function(){

  var addFn = function addFn(){

    var total = 0;
    return function(val){
      total += val;
      return total;
    }

  };

  var add = addFn();

  console.log(add(5));
  console.log(add(20));
  console.log(add(3));
  
}());

0
Avinash Maurya

function Country(){
    console.log("makesure country call");	
   return function State(){
   
    var totalstate = 0;	
	
	if(totalstate==0){	
	
	console.log("makesure statecall");	
	return function(val){
      totalstate += val;	 
      console.log("hello:"+totalstate);
	   return totalstate;
    }	
	}else{
	 console.log("hey:"+totalstate);
	}
	 
  };  
};

var CA=Country();
 
 var ST=CA();
 ST(5); //we have add 5 state
 ST(6); //after few year we requare  have add new 6 state so total now 11
 ST(4);  // 15
 
 var CB=Country();
 var STB=CB();
 STB(5); //5
 STB(8); //13
 STB(3);  //16

 var CX=Country;
 var d=Country();
 console.log(CX);  //store as copy of country in CA
 console.log(d);  //store as return in country function in d

0
Avinash Maurya