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:
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 lasomeClass
instance?
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.
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.
"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.
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 ...
(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
}());
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));
}());
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