168 votes

Comment JavaScript fermetures sont des ordures collectées

Je me suis connecté la suite de Chrome bug, ce qui a conduit à de nombreux problèmes et la non-évidence des fuites de mémoire dans mon code:

(Ces résultats, utilisez google Chrome Dev Tools profileur de mémoire, qui se déroule à la GC, et prend ensuite un tas instantané de tout pour ne pas garbaged collectées.)

Dans le code ci-dessous, someClass exemple garbage collecté (bon):

var someClass = function() {};

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

window.f_ = f();

Mais il ne sera pas nettoyée 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:

screenshot of Chromebug

Il semble qu'un dispositif de fermeture (dans ce cas, function() {}) conserve tous les objets "vivants" si l'objet est référencé par d'autres à la fermeture dans le même contexte, si ou pas si c'fermeture elle-même est même accessible.

Ma question est à propos de la collecte des ordures de la fermeture dans les autres navigateurs (IE 9+ et Firefox). Je suis assez familier avec webkit outils, tels que le JavaScript tas de profils, mais je connais peu d'autres navigateurs, outils, donc je n'ai pas pu tester ce.

Dans lequel de ces trois affaires IE9+ et Firefox ordures recueillir l' someClass exemple?

78voto

some Points 18965

Aussi loin que je peux dire, ce n'est pas un bug mais le comportement attendu.

À partir de Mozilla gestion de la Mémoire à la page: "à compter de 2012, tous les navigateurs modernes expédier un mark-and-sweep ramasse-miettes." "Limitation: les objets doivent être formulées explicitement inaccessible".

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

Mise à jour

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

La norme ne dit rien à propos de la collecte des ordures, mais donne quelques indices de ce qui doit arriver.

  • L'article 13 de définition de Fonction, à l'étape 4: "Laissez-fermeture à être le résultat de la création d'une nouvelle Fonction d'objet comme indiqué dans 13.2"
  • La Section 13.2 "un Environnement Lexical spécifié par Portée (scope = fermeture)
  • La Section 10.2 Lexicale Environnements:

"La référence externe (ou interne) Environnement Lexical est une référence à l'Environnement Lexical qui, logiquement, entoure l'intérieure Environnement Lexical.

Extérieur-Environnement Lexical peut, bien entendu, ses propres extérieur Environnement Lexical. Un Environnement Lexical peut servir que de l'environnement extérieur pour de multiples intérieure Lexicale Les environnements. Par exemple, si une Déclaration de Fonction contient deux imbriqués les Déclarations de Fonction puis Lexicales Les environnements de chacune des fonctions imbriquées auront que leurs externe Environnement Lexical Lexical de la L'environnement de l'exécution actuelle de l'entourage de fonctionner".

Donc, une fonction qui permet d'avoir accès à l'environnement de la mère.

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

Alors pourquoi n'est-il pas toujours disponible?

Il semble que Chrome et FF est assez intelligent pour éliminer la variable dans certains cas, mais dans les deux Opera et IE l' some variable est disponible dans la fermeture (NB: pour voir ce définissez un point d'arrêt sur l' return null et de vérifier le débogueur).

Le GC peut être amélioré pour détecter si some est utilisé ou non dans les fonctions, mais il 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 a aucun moyen de savoir si la variable est utilisé ou non (code testé et fonctionne dans Chrome30, FF25, Opera 12 et IE10).

La mémoire est libérée si la référence à l'objet est brisé par l'attribution d'une autre valeur de window.f_.

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

49voto

Paul Draper Points 14352

J'ai testé cette 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 retrouver avec un tableau de 500 function() {}s', en utilisant un minimum de mémoire.

Malheureusement, ce n'était pas le cas. Chaque fonction vide, tient une (à jamais inaccessible, mais pas de GC ed) tableau de un million de nombres.

Chrome a finalement été arrêté et meurt, Firefox finitions de l'ensemble de la chose après l'utilisation de près de 4 GO de RAM, et IE pousse asymptotiquement plus lent jusqu'à ce qu'il affiche "Out of memory".

La suppression de l'une des lignes commentées des corrections de tout.

Il semble que tous les trois de ces navigateurs (Chrome, Firefox et IE) garder un environnement d'enregistrement par le contexte, et non par la fermeture. Boris émet l'hypothèse que la raison derrière cette décision est de la performance, et qui semble probable, même si je ne suis pas sûr de la façon performante, elle peut être appelée à la lumière de l'expérience.

Si un besoin d'une fermeture de référencement some (certes je n'ai pas l'utiliser ici, mais imaginez que j'ai fait), si, au lieu de

function g() { some; }

J'utilise

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

il va corriger les troubles de la mémoire par le déplacement de la fermeture d'un contexte différent de mon autre fonction.

Cela va me rendre la vie beaucoup plus fastidieux.

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

15voto

Boris Zbarsky Points 22158

Les heuristiques peuvent varier, mais une voie commune pour mettre en œuvre ce genre de chose est de créer un environnement d'enregistrement pour chaque appel à l' f() dans votre cas, et seulement de stocker les habitants de f qui sont effectivement fermés (par certains de fermeture) dans cet environnement d'enregistrement. Puis toute fermeture créé dans l'appel d' f maintient vivante l'environnement d'enregistrement. Je crois que c'est la façon dont Firefox implémente fermetures, au moins.

Cela a l'avantage d'un accès rapide à fermé plus de variables et de simplicité de mise en œuvre. Il a l'inconvénient de l'effet observé, où une courte durée de fermeture de clôture au-dessus de certaines variables causes qu'il soit maintenu en vie par la longue durée de vie des fermetures.

On pourrait tenter de la création de plusieurs environnements d'enregistrements pour les différentes fermetures, en fonction de ce qu'ils ont fait fermer plus de, mais qui peut devenir très compliqué, très rapidement et peut causer des performances et des problèmes de mémoire, de sa propre...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X