J'ai créé un cas de test très simple qui crée une vue Backbone, attache un gestionnaire à un événement et instancie une classe définie par l'utilisateur. Je crois qu'en cliquant sur le bouton "Supprimer" dans 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 des milliards de choses qui apparaissent sur l'instantané du profileur de tas, et je n'ai aucune idée de comment décoder ce qui est bon / mauvais. Les tutoriels que j'ai vus à ce sujet jusqu'à présent me disent simplement d '"utiliser le profileur d'instantané" 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 un outil, ou dois-je vraiment comprendre comment tout a été conçu?
EDIT: Tutoriels comme ceux-ci:
Correction des fuites de mémoire Gmail
Sont représentatifs de certains des matériaux les plus solides, d'après ce que j'ai vu. Cependant, au-delà de l'introduction du concept de la technique des 3 instantanés , 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 à travers un exemple réel, donc sa description conceptuelle vague et générale des choses n'est pas trop utile. Quant à l'exemple "Gmail":
Vous avez donc trouvé une fuite. Maintenant quoi?
Examinez le chemin de rétention des objets ayant fui dans la moitié inférieure du panneau Profils
Si le site d'allocation ne peut pas être facilement déduit (c'est-à-dire les écouteurs d'événements):
Instrumenter le constructeur de l'objet de rétention via la console JS pour enregistrer la trace de pile pour les allocations
Utilisation de la fermeture? Activez l'indicateur existant approprié (c'est-à-dire 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 les choses, pas comment les faire. De mon point de vue, toutes les informations disponibles sont trop vagues ou n'auraient de sens que pour quelqu'un qui a déjà compris le processus.
Certains de ces problèmes plus spécifiques ont été soulevés dans la réponse de @Jonathan Naguin ci-dessous.
la source
main
10 000 fois au lieu d'une seule et voir si vous vous retrouvez avec beaucoup plus de mémoire utilisée à la fin.Réponses:
Un bon flux de travail pour trouver des fuites de mémoire est la technique des trois instantanés , d'abord utilisée par Loreena Lee et l'équipe Gmail pour résoudre certains de leurs problèmes de mémoire. Les étapes sont, en général:
Pour votre exemple, j'ai adapté le code pour montrer ce processus (vous pouvez le trouver ici ) retardant la création de la vue Backbone jusqu'à l'événement de clic du bouton Démarrer. Maintenant:
Vous êtes maintenant prêt à trouver des fuites de mémoire!
Vous remarquerez des nœuds de quelques couleurs différentes. Les nœuds rouges n'ont pas de références directes de Javascript vers eux, mais sont vivants car ils font partie d'une arborescence DOM détachée. Il peut y avoir un nœud dans l'arborescence référencé à partir de Javascript (peut-être en tant que fermeture ou variable) mais empêche par coïncidence l'ensemble de l'arbre DOM d'être récupéré.
Les nœuds jaunes ont cependant des références directes de Javascript. Recherchez les nœuds jaunes dans la même arborescence DOM détachée pour localiser les références de votre Javascript. Il devrait y avoir une chaîne de propriétés menant de la fenêtre DOM à l'élément.
Dans 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".
Sélectionnez la ligne et dans votre console tapez $ 0, vous verrez la fonction et l'emplacement réels:
C'est là que votre élément est référencé. Malheureusement, vous ne pouvez pas faire grand chose, c'est un mécanisme interne de jQuery. Mais, juste à des fins de test, accédez à la fonction et changez la méthode en:
Maintenant, si vous répétez le processus, vous ne verrez aucun nœud rouge :)
Documentation:
la source
$0
fonction dans la console, c'était nouveau pour moi - bien sûr, je n'ai aucune idée de ce que cela fait ou comment vous saviez l'utiliser (cela$1
semble inutile alors que$2
semble faire la même chose). Deuxièmement, comment avez-vous su mettre en surbrillance la ligne#button in function cache()
et aucune des dizaines d'autres lignes? Enfin, il y a des nœuds rouges dedansNodeList
etHTMLInputElement
aussi, mais je ne peux pas les comprendre.cache
ligne contenait des informations alors que les autres n'en contenaient pas? Il existe de nombreuses branches qui ont une distance inférieure à celle-cache
là. Et je ne sais pas comment tu savais que c'étaitHTMLInputElement
un enfant deHTMLDivElement
. Je le vois référencé à l'intérieur ("native in HTMLDivElement"), mais il se réfère également à lui-même et à deuxHTMLButtonElement
s, ce qui n'a pas de sens pour moi. J'apprécie certainement que vous ayez identifié la réponse pour cet exemple, mais je ne saurais vraiment pas comment généraliser cela à d'autres questions.Filter objects allocated between Snapshots 1 and 2 in Snapshot 3's "Summary" view.
dire?Voici une astuce sur le profilage de la mémoire d'un jsfiddle: Utilisez l'URL suivante pour isoler votre résultat jsfiddle, cela supprime tout le framework jsfiddle et ne charge que votre résultat.
http://jsfiddle.net/4QhR2/show/
Je n'ai jamais pu comprendre comment utiliser la chronologie et le profileur pour détecter les fuites de mémoire, jusqu'à ce que je lis la documentation suivante. Après avoir lu la section intitulée «Object allocation tracker», j'ai pu utiliser l'outil «Record Heap Allocations» et suivre certains nœuds DOM détachés.
J'ai résolu le problème en passant de la liaison d'événements jQuery à l'utilisation de la délégation d'événements Backbone. Je crois comprendre que les nouvelles versions de Backbone dissocieront automatiquement les événements pour vous si vous appelez
View.remove()
. Exécutez certaines des démos vous-même, elles sont configurées avec des fuites de mémoire que vous devez identifier. N'hésitez pas à poser des questions ici si vous ne l'avez toujours pas après avoir étudié cette documentation.https://developers.google.com/chrome-developer-tools/docs/javascript-memory-profiling
la source
En gros, vous devez regarder le nombre d'objets dans votre instantané de tas. Si le nombre d'objets augmente entre deux instantanés et que vous avez supprimé des objets, 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.
la source
Window/http://jsfiddle.net/4QhR2/show
pourrait être utile, mais ce n'est que des fonctions infinies. Je n'ai aucune idée de ce qui se passe là-dedans.Il existe une vidéo d'introduction de Google, qui sera très utile pour trouver des fuites de mémoire JavaScript.
https://www.youtube.com/watch?v=L3ugr9BJqIs
la source
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 de nœuds DOM et d'événements.
Si le graphique de mémoire indique effectivement une fuite de mémoire, vous pouvez utiliser le profileur pour déterminer ce qui fuit.
la source
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 façon de confirmer et de localiser une fuite de mémoire à l'aide de la comparaison d'instantanés de tas et des différentes vues d'instantanés hep disponibles.
la source
J'appuie le conseil de prendre un instantané de tas, ils sont excellents pour détecter les fuites de mémoire, chrome fait un excellent travail de capture instantanée.
Dans mon projet de recherche pour mon diplôme, je construisais une application Web interactive qui devait générer beaucoup de données accumulées en `` couches '', beaucoup de ces couches seraient `` supprimées '' dans l'interface utilisateur, mais pour une raison quelconque, la mémoire ne l'était pas. étant désalloué, à l'aide de l'outil de capture instantanée, j'ai pu déterminer que JQuery avait conservé une référence sur l'objet (la source était lorsque j'essayais de déclencher un
.load()
événement qui gardait la référence malgré le fait que je sois hors de portée). Avoir ces informations à portée de main a sauvé mon projet à lui seul, c'est un outil très utile lorsque vous utilisez les bibliothèques d'autres personnes et que vous avez ce problème de références persistantes qui empêchent 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é à prendre des instantanés, émettre des hypothèses sur ce qui pourrait causer le problème et tester chaque scénario, en créant des instantanés avant et après.
la source
Quelques remarques importantes concernant l'identification des fuites de mémoire à l'aide des outils de développement Chrome:
1) Chrome lui-même a des fuites de mémoire pour certains éléments tels que les champs de mot de passe et de nombre. https://bugs.chromium.org/p/chromium/issues/detail?id=967438 . Évitez de les utiliser lors du débogage car ils polissent votre instantané de tas lors de la recherche d'éléments détachés.
2) Évitez d'enregistrer quoi que ce soit dans la console du navigateur. Chrome ne récupérera pas les objets écrits sur la console, ce qui affectera votre résultat. Vous pouvez supprimer la sortie en plaçant le code suivant au début de votre script / page:
3) Utilisez des instantanés de tas et recherchez "détacher" pour identifier les éléments DOM détachés. En survolant des objets, vous avez accès à toutes les propriétés, y compris id et externalHTML, qui peuvent aider à identifier chaque élément. Si les éléments détachés sont encore trop génériques pour être reconnus, attribuez-leur des ID uniques à l'aide de la console du navigateur avant d'exécuter votre test, par exemple:
Maintenant, lorsque vous identifiez un élément détaché avec, disons id = "AutoId_49", rechargez votre page, exécutez à nouveau l'extrait de code ci-dessus et recherchez l'élément avec id = "AutoId_49" en utilisant l'inspecteur DOM ou document.querySelector (..) . Naturellement, cela ne fonctionne que si le contenu de votre page est prévisible.
Comment j'exécute mes tests pour identifier les fuites de mémoire
1) Charger la page (avec la sortie de la console supprimée!)
2) Faites des choses sur la page qui pourraient entraîner des fuites de mémoire
3) Utilisez les outils de développement pour prendre un instantané du tas et rechercher «détacher»
4) Survolez les éléments pour les identifier à partir de leurs propriétés id ou externalHTML
la source