web-dev-qa-db-fra.com

Recherche de fuites de mémoire JavaScript avec Chrome

J'ai créé un scénario de test très simple qui crée une vue Backbone, associe un gestionnaire à un événement et instancie une classe définie par l'utilisateur. Je crois qu'en cliquant sur le bouton "Supprimer" de cet exemple, tout sera nettoyé et il ne devrait y avoir aucune fuite de mémoire.

Un jsfiddle pour le code est ici: http://jsfiddle.net/4QhR2/

// scope everything to a function
function main() {

    function MyWrapper() {
        this.element = null;
    }
    MyWrapper.prototype.set = function(elem) {
        this.element = elem;
    }
    MyWrapper.prototype.get = function() {
        return this.element;
    }

    var MyView = Backbone.View.extend({
        tagName : "div",
        id : "view",
        events : {
            "click #button" : "onButton",
        },    
        initialize : function(options) {        
            // done for demo purposes only, should be using templates
            this.html_text = "<input type='text' id='textbox' /><button id='button'>Remove</button>";        
            this.listenTo(this,"all",function(){console.log("Event: "+arguments[0]);});
        },
        render : function() {        
            this.$el.html(this.html_text);

            this.wrapper = new MyWrapper();
            this.wrapper.set(this.$("#textbox"));
            this.wrapper.get().val("placeholder");

            return this;
        },
        onButton : function() {
            // assume this gets .remove() called on subviews (if they existed)
            this.trigger("cleanup");
            this.remove();
        }
    });

    var view = new MyView();
    $("#content").append(view.render().el);
}

main();

Cependant, je ne sais pas comment utiliser le profileur de Google Chrome pour vérifier que c'est bien le cas. Il y a un tas de milliards de choses qui apparaissent sur l'instantané du profileur de tas, et je ne sais pas comment décoder ce qui est bon ou mauvais. Les didacticiels que j'ai vus jusqu'ici me disent simplement "d'utiliser le profileur d'instantanés" ou me donnent un manifeste extrêmement détaillé sur le fonctionnement de l'ensemble du profileur. Est-il possible d'utiliser simplement le profileur comme outil ou dois-je vraiment comprendre comment tout a été conçu?

EDIT: Des tutoriels comme ceux-ci:

Correction de fuite de mémoire Gmail

tilisation de DevTools

Sont représentatifs de certains des éléments les plus puissants, d'après ce que j'ai vu. Cependant, au-delà de l'introduction du concept de 3 Snapshot Technique , je trouve qu'ils offrent très peu de connaissances pratiques (pour un débutant comme moi). Le didacticiel "Utilisation de DevTools" ne fonctionne pas avec un exemple réel. Par conséquent, sa description conceptuelle vague et générale des choses n'est pas très utile. En ce qui concerne l'exemple "Gmail":

Vous avez donc trouvé une fuite. Maintenant quoi?

  • Examinez le chemin de rétention des objets présentant une fuite dans la moitié inférieure du panneau Profils.

  • Si le site d’allocation ne peut pas être facilement déduit (par exemple, les écouteurs d’événements):

  • Instrumentez le constructeur de l'objet retenu via la console JS pour enregistrer la trace de pile pour les allocations

  • Utilisation de la fermeture? Activez l'indicateur existant approprié (par exemple, goog.events.Listener.ENABLE_MONITORING) pour définir la propriété creationStack pendant la construction.

Je me trouve plus confus après avoir lu cela, pas moins. Et, encore une fois, cela me dit simplement de faire des choses, pas comment de les faire. De mon point de vue, toute l'information disponible est trop vague ou n'aurait de sens que pour quelqu'un qui a déjà compris le processus.

Certaines de ces questions plus spécifiques ont été soulevées dans @ réponse de Jonathan Naguin ci-dessous.

157
EleventyOne

La technique à trois captures instantanées , utilisée pour la première fois par Loreena Lee et l'équipe Gmail pour résoudre certains de leurs problèmes de mémoire, constitue un bon flux de travail pour la recherche de fuites de mémoire. Les étapes sont, en général:

  • Prenez un instantané de tas.
  • Faire des trucs.
  • Prenez un autre instantané de tas.
  • Répétez les mêmes choses.
  • Prenez un autre instantané de tas.
  • Filtrez les objets alloués entre les instantanés 1 et 2 dans la vue "Résumé" de l'instantané 3.

Pour votre exemple, j'ai adapté le code pour afficher ce processus (vous pouvez le trouver ici ) en retardant la création de la vue Backbone jusqu'à l'événement click du bouton Démarrer. Maintenant:

  • Exécutez le code HTML (enregistré localement à l’aide de adresse ) et prenez un instantané.
  • Cliquez sur Démarrer pour créer la vue.
  • Prenez un autre instantané.
  • Cliquez sur supprimer.
  • Prenez un autre instantané.
  • Filtrez les objets alloués entre les instantanés 1 et 2 dans la vue "Résumé" de l'instantané 3.

Vous êtes maintenant prêt à rechercher des fuites de mémoire!

Vous remarquerez des nœuds de quelques couleurs différentes. Les nœuds rouges ne sont pas directement référencés depuis Javascript, mais sont vivants car ils font partie d'un arbre DOM détaché. Il peut y avoir un nœud dans l’arborescence référencée à partir de Javascript (peut-être en tant que fermeture ou variable), mais cela empêche, par coïncidence, que l’ensemble de l’arborescence DOM ne soit récupérée.

enter image description here

Les nœuds jaunes ont cependant des références directes à partir de Javascript. Recherchez les nœuds jaunes dans la même arborescence DOM détachée pour localiser les références à partir de votre Javascript. Il devrait y avoir une chaîne de propriétés menant de la fenêtre DOM à l'élément.

En votre particulier, vous pouvez voir un élément HTML Div marqué en rouge. Si vous développez l'élément, vous verrez qu'il est référencé par une fonction "cache".

enter image description here

Sélectionnez la ligne et dans votre console tapez $ 0, vous verrez la fonction et l'emplacement actuels:

>$0
function cache( key, value ) {
        // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
        if ( keys.Push( key += " " ) > Expr.cacheLength ) {
            // Only keep the most recent entries
            delete cache[ keys.shift() ];
        }
        return (cache[ key ] = value);
    }                                                     jquery-2.0.2.js:1166

C'est à cet endroit que votre élément est référencé. Malheureusement, vous n’avez pas grand chose à faire, c’est un mécanisme interne de jQuery. Mais, juste pour tester, allez dans la fonction et changez la méthode pour:

function cache( key, value ) {
    return value;
}

Maintenant, si vous répétez le processus, vous ne verrez aucun nœud rouge :)

Documentation:

195
Jonathan Naguin

Voici un conseil sur le profilage de la mémoire d'un jsfiddle: utilisez l'URL suivante pour isoler votre résultat jsfiddle, il supprime tout le cadre de jsfiddle et ne charge que votre résultat.

http://jsfiddle.net/4QhR2/show/

Je n'ai jamais réussi à comprendre comment utiliser Timeline et Profiler pour localiser les fuites de mémoire, jusqu'à ce que je lise la documentation suivante. Après avoir lu la section intitulée "Suivi de l'affectation d'objet", j'ai pu utiliser l'outil "Enregistrer les allocations de tas" et suivre certains nœuds DOM détachés.

J'ai résolu le problème en passant de la liaison d'événement jQuery à la délégation d'événements Backbone. Si j'ai appelé View.remove(), les versions les plus récentes de Backbone seront automatiquement dissociées des événements. Exécutez vous-même certaines des démos, elles sont configurées avec des fuites de mémoire que vous pouvez identifier. N'hésitez pas à poser des questions ici si vous ne l'obtenez toujours pas après avoir étudié cette documentation.

https://developers.google.com/chrome-developer-tools/docs/javascript-memory-profiling

7
ricksuggs

Fondamentalement, vous devez examiner le nombre d'objets contenus dans votre instantané de segment de mémoire. Si le nombre d'objets augmente entre deux instantanés et que vous en avez disposé, vous avez une fuite de mémoire. Mon conseil est de rechercher dans votre code des gestionnaires d'événements qui ne se détachent pas.

6
Konstantin Dinev

Il existe une vidéo d'introduction de Google qui sera très utile pour rechercher les fuites de mémoire JavaScript.

https://www.youtube.com/watch?v=L3ugr9BJqIs

4
令狐葱

Vous pouvez également consulter l'onglet Chronologie dans les outils de développement. Enregistrez l'utilisation de votre application et gardez un œil sur le nombre d'écouteurs DOM Node et d'événements.

Si le graphique de la mémoire indique effectivement une fuite de mémoire, vous pouvez utiliser le profileur pour déterminer ce qui fuit.

3
Robert Falkén

Vous voudrez peut-être aussi lire:

http://addyosmani.com/blog/taming-the-Unicorn-easing-javascript-memory-profiling-in-devtools/

Il explique l'utilisation des outils de développement chrome et donne des conseils étape par étape sur la procédure de confirmation et de localisation d'une fuite de mémoire à l'aide de la comparaison d'instantané de segment mémoire et des différentes vues d'instantané hep disponibles.

3
bennidi

Je seconde le conseil de prendre un instantané de tas, ils sont excellents pour détecter les fuites de mémoire, chrome fait un excellent travail d’instantané.

Dans le cadre de mon projet de recherche, je construisais une application Web interactive qui devait générer beaucoup de données constituées de "couches". Nombre de ces couches seraient "supprimées" dans l'interface utilisateur, mais pour une raison quelconque, la mémoire n'était pas En étant désalloué, à l'aide de l'outil de capture instantanée, j'ai pu déterminer que JQuery conservait une référence sur l'objet (la source était lorsque j'essayais de déclencher un événement .load() qui conservait la référence malgré sa sortie du champ d'application). Avoir ces informations à portée de main a sauvé mon projet, c'est un outil très utile lorsque vous utilisez les bibliothèques d'autres personnes et vous avez ce problème de références en attente qui empêche le GC de faire son travail.

EDIT: Il est également utile de planifier à l’avance les actions que vous allez effectuer pour minimiser le temps passé à la prise d’instantanés, de supposer l’origine du problème et de tester chaque scénario, en créant des instantanés avant et après.

2