web-dev-qa-db-fra.com

Fuites et fermetures de mémoire en JavaScript - quand et pourquoi?

Vous lisez assez souvent sur le Web que l'utilisation de fermetures est une source massive de fuites de mémoire en JavaScript. La plupart du temps, ces articles font référence au mélange de code de script et d'événements DOM, où le script pointe vers le DOM et vice-versa.

Je comprends que les fermetures peuvent y être un problème.

Mais qu'en est-il de Node.js? Ici, nous n'avons naturellement pas de DOM - il n'y a donc aucune chance d'avoir des effets secondaires de fuite de mémoire comme dans les navigateurs.

Quels autres problèmes peut-il y avoir avec les fermetures? Quelqu'un peut-il élaborer ou me diriger vers un bon tutoriel à ce sujet?

Veuillez noter que cette question cible explicitement Node.js, et non le navigateur.

50
Golo Roden

Cette question demande quelque chose de similaire. Fondamentalement, l'idée est que si vous utilisez une fermeture dans un rappel, vous devez "désinscrire" le rappel lorsque vous avez terminé pour que le GC sache qu'il ne peut pas être rappelé. Cela me semble logique; si vous avez une fermeture qui n'attend que d'être appelée, le GC aura du mal à savoir que vous en avez terminé. En supprimant manuellement la fermeture du mécanisme de rappel, elle devient non référencée et disponible pour la collecte.

En outre, Mozilla a publié n excellent article sur la recherche de fuites de mémoire dans Node.js code. Je suppose que si vous essayez certaines de leurs stratégies, vous pourriez trouver des parties de votre code qui expriment un comportement qui fuit. Les meilleures pratiques sont Nice et tout, mais je pense qu'il est plus utile de comprendre les besoins de votre programme et de trouver des meilleures pratiques personnalisées en fonction de ce que vous pouvez observer empiriquement.

Voici un court extrait de l'article de Mozilla:

  • node-mtrace De Jimb Esser, qui utilise l'utilitaire GCC mtrace pour profiler l'utilisation du tas.
  • node-heap-dump De Dave Pacheco prend un instantané du tas V8 et sérialise le tout dans un énorme fichier JSON. Il comprend des outils pour parcourir et analyser l'instantané résultant en JavaScript.
  • v8-profiler Et node-inspector De Danny Coates fournissent des liaisons Node pour le profileur V8 et une interface de débogage Node à l'aide de WebKit Web Inspector.
  • La fourche de Felix Gnass de la même chose qui désactive le graphique de retenue
  • Le didacticiel Node sur les fuites de mémoire de Felix Geisendörfer est une courte et douce explication sur la façon d'utiliser les v8-profiler Et node-debugger, Et est actuellement à la pointe de la technologie pour la plupart Débogage de fuite de mémoire Node.js.
  • La plate-forme SmartOS de Joyent, qui fournit un arsenal d'outils à votre disposition pour déboguer les fuites de mémoire de Node.js

Les réponses à cette question disent essentiellement que vous pouvez aider le GC en affectant null aux variables de fermeture.

var closureVar = {};
doWork(function callback() {
  var data = closureVar.usefulData;
  // Do a bunch of work
  closureVar = null;
});

Toutes les variables déclarées à l'intérieur une fonction disparaîtra lorsque la fonction retourne, sauf celles qui sont utilisées dans d'autres fermetures. Dans cet exemple, closureVar doit être en mémoire jusqu'à ce que callback() soit appelé, mais qui sait quand cela se produira? Une fois le rappel appelé, vous pouvez donner un indice au GC en définissant votre variable de fermeture sur null.

[~ # ~] clause de non-responsabilité [~ # ~] : Comme vous pouvez le voir dans les commentaires ci-dessous, il y a quelques utilisateurs de [SO _ qui disent que ces informations sont obsolètes et sans conséquence pour Node.js. Je n'ai pas encore de réponse définitive à ce sujet; Je poste simplement ce que j'ai trouvé sur le Web.

36
RustyTheBoyRobot

Vous pouvez trouver un bon exemple et une bonne explication dans cet article de blog par David Glasser.

Eh bien, le voici (j'ai ajouté quelques commentaires):

var theThing = null;
var cnt = 0; // helps us to differentiate the leaked objects in the debugger
var replaceThing = function () {
    var originalThing = theThing;
    var unused = function () {
        if (originalThing) // originalThing is used in the closure and hence ends up in the lexical environment shared by all closures in that scope
            console.log("hi");
    };
    // originalThing = null; // <- nulling originalThing here tells V8 gc to collect it 
    theThing = {
        longStr: (++cnt) + '_' + (new Array(1000000).join('*')),
        someMethod: function () { // if not nulled, original thing is now attached to someMethod -> <function scope> -> Closure
            console.log(someMessage);
        }
    };
};
setInterval(replaceThing, 1000);

Veuillez l'essayer avec et sans annuler originalThing dans Chrome Dev Tools (onglet chronologie, vue mémoire, cliquez sur enregistrement). Notez que l'exemple ci-dessus s'applique au navigateur et à Node.js environnements.

Nous remercions également et surtout Vyacheslav Egorov .

10
borisdiakur

Je dois être en désaccord avec les fermetures étant une cause de fuites de mémoire. Cela peut être vrai pour les anciennes versions de IE en raison de sa collecte de déchets de mauvaise qualité. Veuillez lire this article de Douglas Crockford, qui indique clairement ce qu'est une fuite de mémoire.

La mémoire non récupérée aurait fui.

Les fuites ne sont pas le problème, une collecte efficace des ordures l'est. Des fuites peuvent se produire dans les applications JavaScript du navigateur et du serveur. Prenons l'exemple du V8. Dans le navigateur, la récupération de place s'effectue sur un onglet lorsque vous passez à une fenêtre/un onglet différent. La fuite est colmatée au ralenti. Les onglets peuvent être inactifs.

Sur le serveur, les choses ne sont pas si faciles. Des fuites peuvent se produire, mais le GC n'est pas aussi rentable. Les serveurs ne peuvent pas se permettre de GC fréquemment ou ses performances seront affectées. Lorsqu'un processus de nœud atteint une certaine utilisation de la mémoire, il démarre le GC. Les fuites seront ensuite supprimées périodiquement. Mais les fuites peuvent toujours se produire à un rythme plus rapide, entraînant le plantage des programmes.

2
user568109