web-dev-qa-db-fra.com

Comment les fermetures de JavaScript sont récupérées

J'ai enregistré le bug de Chrome suivant, ce qui a entraîné de nombreuses fuites de mémoire graves et non évidentes dans mon code:

(Ces résultats utilisent le profileur de mémoire de Chrome Dev Tools, qui exécute le GC, puis prend un cliché instantané de tout ce qui n'est pas récupéré.

Dans le code ci-dessous, l'instance someClass est récupérée (bonne):

var someClass = function() {};

function f() {
  var some = new someClass();
  return function() {};
}

window.f_ = f();

Mais ce ne seront pas des ordures collectées dans ce cas (mauvais):

var someClass = function() {};

function f() {
  var some = new someClass();
  function unreachable() { some; }
  return function() {};
}

window.f_ = f();

Et la capture d'écran correspondante:

screenshot of Chromebug

Il semble qu'une fermeture (dans ce cas, function() {}) garde tous les objets "en vie" si l'objet est référencé par une autre fermeture dans le même contexte, que cette fermeture soit elle-même accessible ou non.

Ma question concerne la récupération de place de la fermeture dans d'autres navigateurs (IE 9+ et Firefox). Je connais assez bien les outils de Webkit, tels que le profileur de tas JavaScript, mais je connais peu les outils des autres navigateurs. Je n'ai donc pas été en mesure de le tester.

Dans lequel de ces trois cas IE9 + et Firefox garbage collecteront-ils lasomeClassinstance?

165
Paul Draper

J'ai testé cela dans IE9 + et Firefox.

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);

Site en direct ici .

J'espérais pouvoir me retrouver avec un tableau de 500 function() {}, en utilisant un minimum de mémoire.

Malheureusement, ce n'était pas le cas. Chaque fonction vide conserve un tableau (pour toujours inaccessible, mais non GC) d'un million de nombres.

Chrome finit par s’arrêter et meurt, Firefox finit par tout utiliser après avoir utilisé près de 4 Go de RAM et IE ralentit de manière asymptotique jusqu’à ce que «Mémoire insuffisante» s'affiche.

Supprimer l'une des lignes commentées corrige tout.

Il semble que ces trois navigateurs (Chrome, Firefox et IE) conservent un enregistrement d'environnement par contexte et non par fermeture. Boris émet l'hypothèse que la raison derrière cette décision est la performance, ce qui semble probable, bien que je ne sois pas sûr de la performance qui peut être appelée à la lumière de l'expérience ci-dessus.

Si vous avez besoin d’une fermeture faisant référence à some (d’accord, je ne l’ai pas utilisée ici, mais imaginez que je l’ai fait), si au lieu de

function g() { some; }

J'utilise

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

cela résoudra les problèmes de mémoire en déplaçant la fermeture dans un contexte différent de celui de mon autre fonction.

Cela rendra ma vie beaucoup plus fastidieuse.

P.S. Par curiosité, j'ai essayé ceci en Java (en utilisant sa capacité à définir des classes à l'intérieur de fonctions). GC fonctionne comme je l'avais initialement espéré pour le Javascript.

48
Paul Draper

Autant que je sache, ce n'est pas un bug, mais le comportement attendu. 

Extrait de la page de gestion de la mémoire de Mozilla: "À partir de 2012, tous les navigateurs modernes intègrent un ramasse-miettes avec marquage et balayage." "Limitation: les objets doivent être explicitement inaccessibles"

Dans vos exemples où cela échoue, some est toujours accessible dans la fermeture. J'ai essayé deux façons de le rendre inaccessible et les deux fonctionnent. Soit vous définissez some=null lorsque vous n'en avez plus besoin, soit vous définissez window.f_ = null; et il ne sera plus nécessaire. 

Mettre à jour

Je l'ai essayé dans Chrome 30, FF25, Opera 12 et IE10 sous Windows.

Le standard ne dit rien sur la récupération de place, mais donne quelques indices sur ce qui devrait se passer. 

  • Section 13 Définition de la fonction, étape 4: "La fermeture est le résultat de la création d'un nouvel objet Function, comme spécifié en 13.2"
  • Section 13.2 "Un environnement lexical spécifié par Portée" (portée = fermeture)
  • Section 10.2 Environnements lexicaux:

"La référence externe d’un environnement lexical (interne) est une référence à l’environnement lexical qui, logiquement, entoure l’environnement lexical interne. 

Bien entendu, un environnement lexical externe peut avoir son propre extérieur Environnement lexical. Un environnement lexical peut servir d’environnement externe à plusieurs systèmes Lexical internes Environnements. Par exemple, si une déclaration Function contient deux déclarations imbriquées Function, le Lexical Les environnements de chacune des fonctions imbriquées auront pour environnement lexical externe le Lexical Environnement de l'exécution en cours de la fonction environnante. "

Ainsi, une fonction aura accès à l'environnement du parent.

Donc, some devrait être disponible lors de la fermeture de la fonction de retour.

Alors pourquoi n'est-il pas toujours disponible? 

Il semble que Chrome et FF soient suffisamment intelligents pour éliminer la variable dans certains cas, mais dans Opera et IE, la variable some est disponible dans la fermeture (NB: pour afficher ceci, définissez un point d'arrêt sur return null et vérifiez le débogueur. ).

Le CPG pourrait être amélioré pour détecter si some est utilisé ou non dans les fonctions, mais ce sera compliqué.

Un mauvais exemple:

var someClass = function() {};

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

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

Dans l'exemple ci-dessus, le GC n'a aucun moyen de savoir si la variable est utilisée ou non (code testé et fonctionne sous Chrome30, FF25, Opera 12 et IE10).

La mémoire est libérée si la référence à l'objet est rompue en attribuant une autre valeur à window.f_.

A mon avis, ce n'est pas un bug.

77
some

Les heuristiques varient, mais un moyen courant d'implémenter ce genre de choses est de créer un enregistrement d'environnement pour chaque appel à f() dans votre cas et de ne stocker que les sections locales de f qui sont réellement fermées (par certains fermeture) cet enregistrement d'environnement. Ensuite, toute fermeture créée dans l'appel à f conserve l'enregistrement de l'environnement. Je crois que c'est comme ça que Firefox implémente les fermetures, au moins.

Cela présente les avantages d'un accès rapide à des variables fermées et d'une simplicité de mise en œuvre. Il présente l’inconvénient de l’effet observé, dans la mesure où une fermeture de courte durée sur une variable entraîne sa survie par des fermetures de longue durée.

On pourrait essayer de créer plusieurs enregistrements d'environnement pour différentes fermetures, en fonction de ce qui est réellement fermé, mais cela peut devenir très compliqué très rapidement et causer des problèmes de performances et de mémoire ...

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. Maintenir l’état entre les appels de fonction Supposons que vous avez function add () et que vous souhaitiez qu’il ajoute toutes les valeurs qui lui sont transmises lors de plusieurs appels et renvoie la somme. 

comme add (5); // renvoie 5

ajouter (20); // retourne 25 (5 + 20)

ajouter (3); // retourne 28 (25 + 3)

il est normal de définir une variable globale Bien sûr, vous pouvez utiliser une variable globale pour conserver le total. Mais gardez à l'esprit que ce mec va vous manger vivant si vous utilisez des globals.

maintenant dernière manière en utilisant la fermeture sans définir variable globale

(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