J'ai enregistré le bogue Chrome suivant , qui a conduit à 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 instantané du tas de tout ce qui n'a pas été collecté.)
Dans le code ci-dessous, l' someClass
instance est garbage collecté (bon):
var someClass = function() {};
function f() {
var some = new someClass();
return function() {};
}
window.f_ = f();
Mais ce ne sera pas ramassé 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:
Il semble qu'une fermeture (dans ce cas, function() {}
) garde tous les objets «vivants» si l'objet est référencé par une autre fermeture dans le même contexte, que cette fermeture soit même accessible ou non.
Ma question porte sur le ramassage des ordures de 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, donc je n'ai pas pu tester cela.
Dans lequel de ces trois cas IE9 + et Firefox collecteront-ils l' someClass
instance?
unreachable
fonction n'est jamais exécutée, donc rien n'est réellement enregistré.Réponses:
Autant que je sache, ce n'est pas un bug mais le comportement attendu.
À partir de la page de gestion de la mémoire de Mozilla : «À partir de 2012, tous les navigateurs modernes embarquent un ramasse-miettes avec marquage et balayage.» "Limitation: les objets doivent être rendus explicitement inaccessibles " .
Dans vos exemples où il échoue, il
some
est toujours accessible dans la fermeture. J'ai essayé deux façons de le rendre inaccessible et les deux fonctionnent. Soit vous définissezsome=null
lorsque vous n'en avez plus besoin, soit vous définissezwindow.f_ = null;
et il disparaîtra.Mettre à jour
Je l'ai essayé dans Chrome 30, FF25, Opera 12 et IE10 sous Windows.
La norme ne dit rien sur le ramasse-miettes, mais donne quelques indices sur ce qui devrait se passer.
Ainsi, une fonction aura accès à l'environnement du parent.
Donc,
some
devrait être disponible dans 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
some
variable est disponible dans la fermeture (NB: pour afficher cela, définissez un point d'arrêtreturn null
et vérifiez le débogueur).Le GC pourrait être amélioré pour détecter s'il
some
est utilisé ou non dans les fonctions, mais ce sera compliqué.Un mauvais exemple:
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_
.À mon avis, ce n'est pas un bug.
la source
setTimeout()
rappel exécuté, la portée de la fonction dusetTimeout()
rappel est terminée et toute cette portée doit être garbage collection, libérant sa référence àsome
. Il n'y a plus de code exécutable pouvant atteindre l'instance desome
dans la fermeture. Il devrait être ramassé des ordures. Le dernier exemple est encore pire car ilunreachable()
n'est même pas appelé et personne n'y fait référence. Sa portée devrait également être GC. Ces deux semblent être des bugs. Il n'y a aucune exigence de langage dans JS pour «libérer» des éléments dans une portée de fonction.f()
appelé, il n'y a plus de références réellessome
. Il est inaccessible et doit être GC.eval
c'est un cas vraiment spécial. Par exemple,eval
ne peut pas être aliasé ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ), par exemplevar eval2 = eval
. Sieval
est utilisé (et comme il ne peut pas être appelé par un nom différent, c'est facile à faire), alors nous devons supposer qu'il peut utiliser n'importe quoi dans la portée.J'ai testé cela dans IE9 + et Firefox.
Site en direct ici .
J'espérais me retrouver avec un tableau de 500
function() {}
, en utilisant une mémoire minimale.Ce n’était malheureusement pas le cas. Chaque fonction vide tient à un tableau (à jamais inaccessible, mais pas GC) d'un million de nombres.
Chrome s'arrête finalement et meurt, Firefox termine le tout après avoir utilisé près de 4 Go de RAM, et IE devient asymptotiquement plus lent jusqu'à ce qu'il affiche "Out of memory".
La suppression de 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 de cette décision est la performance, et cela semble probable, même si je ne suis pas sûr de la performance que cela peut avoir à la lumière de l'expérience ci-dessus.
Si vous avez besoin d'un référencement de fermeture
some
(d'accord, je ne l'ai pas utilisé ici, mais imaginez que je l'ai fait), si au lieu dej'utilise
cela résoudra les problèmes de mémoire en déplaçant la fermeture dans un contexte différent de mon autre fonction.
Cela rendra ma vie beaucoup plus fastidieuse.
PS Par curiosité, j'ai essayé cela en Java (en utilisant sa capacité à définir des classes à l'intérieur des fonctions). GC fonctionne comme je l'avais initialement espéré pour Javascript.
la source
Les heuristiques varient, mais une manière courante d'implémenter ce genre de chose consiste à créer un enregistrement d'environnement pour chaque appel
f()
dans votre cas, et à ne stocker que les sections localesf
qui sont effectivement fermées (par une fermeture) dans cet enregistrement d'environnement. Ensuite, toute fermeture créée dans l'appel àf
maintient en vie l'enregistrement d'environnement. Je pense que c'est ainsi que Firefox implémente au moins les fermetures.Cela présente les avantages d'un accès rapide aux variables fermées et d'une simplicité de mise en œuvre. Elle présente l'inconvénient de l'effet observé, où une fermeture de courte durée sur une variable la maintient en vie 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 qu'ils ferment réellement, mais cela peut devenir très compliqué très rapidement et peut causer des problèmes de performances et de mémoire.
la source
O(n^2)
ouO(2^n)
comme une explosion, mais pas une augmentation proportionnelle.comme add (5); // renvoie 5
ajouter (20); // renvoie 25 (5 + 20)
ajouter (3); // renvoie 28 (25 + 3)
la première façon de procéder est de définir une variable globale. Bien sûr, vous pouvez utiliser une variable globale pour contenir le total. Mais gardez à l'esprit que ce mec vous mangera vivant si vous (ab) utilisez des globals.
maintenant la dernière façon d' utiliser la fermeture sans définir la variable globale
la source
la source
la source