Méthode de traitement : 1. Attribuez null ou réattribuez d'autres valeurs après utilisation ; 2. Utilisez des algorithmes de récupération de place modernes ; 3. Soyez prudent lors de l'enregistrement des références d'éléments DOM 4. Lisez l'application via le problème SessionStack pour éviter les fuites de mémoire et augmenter l'utilisation de la mémoire de l'ensemble de l'application.
L'environnement d'exploitation de ce tutoriel : système Windows 7, JavaScript version 1.8.5, ordinateur Dell G3. ,
Les fuites de mémoire sont un problème auquel tout développeur sera éventuellement confronté. Elles sont à l'origine de nombreux problèmes : réponse lente, plantages, latence élevée et autres problèmes d'application.
Essentiellement, une fuite de mémoire peut être définie comme : lorsque l'application n'a plus besoin d'occuper la mémoire, pour une raison quelconque, la mémoire n'est pas récupérée par le système d'exploitation ou le pool de mémoire disponible. Les langages de programmation varient dans la manière dont ils gèrent la mémoire. Seuls les développeurs savent le mieux quelle mémoire n'est plus nécessaire et peut être récupérée par le système d'exploitation. Certains langages de programmation fournissent des fonctionnalités linguistiques qui aident les développeurs à faire ce genre de choses. D'autres comptent sur les développeurs pour savoir clairement si de la mémoire est nécessaire.
JavaScript est un langage de récupération de place. Les langages de récupération de place aident les développeurs à gérer la mémoire en vérifiant périodiquement si la mémoire précédemment allouée est accessible. En d'autres termes, les langages récupérés atténuent les problèmes de « la mémoire est toujours disponible » et de « la mémoire est toujours accessible ». La différence entre les deux est subtile mais importante : seul le développeur sait quelle mémoire sera encore utilisée à l'avenir, tandis que la mémoire inaccessible est déterminée et marquée par un algorithme, et est rapidement récupérée par le système d'exploitation.
La principale cause des fuites de mémoire dans les langages ramassés est les références indésirables. Avant de le comprendre, vous devez comprendre comment le langage de récupération de place fait la distinction entre la mémoire accessible et inaccessible.
L'algorithme utilisé par la plupart des langages de récupération de place est appelé Mark-and-sweep. L'algorithme comprend les étapes suivantes :
Le ramasse-miettes crée une liste de "racines". Les racines sont généralement des références à des variables globales dans votre code. En JavaScript, l'objet "window" est une variable globale et est traité comme racine. L'objet window existe toujours, donc le garbage collector peut vérifier que lui et tous ses objets enfants existent (c'est-à-dire qu'ils ne sont pas des déchets)
Toutes les racines sont vérifiées et marquées comme actives (c'est-à-dire qu'elles ne sont pas des déchets). Tous les sous-objets sont également vérifiés de manière récursive. Tous les objets commençant par root ne sont pas considérés comme des déchets s'ils sont accessibles.
Toute la mémoire non marquée sera traitée comme une poubelle, et le collecteur peut désormais libérer la mémoire et la restituer au système d'exploitation.
Les garbage collector modernes ont amélioré leurs algorithmes, mais l'essence est la même : la mémoire accessible est marquée et le reste est récupéré.
Une référence inutile signifie que le développeur sait que la référence mémoire n'est plus nécessaire, mais que pour une raison quelconque, elle est toujours laissée dans l'arborescence racine active. En JavaScript, une référence indésirable est une variable qui reste dans le code et n'est plus nécessaire mais pointe vers un morceau de mémoire qui doit être libéré. Certaines personnes pensent qu'il s'agit d'une erreur du développeur.
Pour comprendre les fuites de mémoire les plus courantes en JavaScript, nous devons comprendre comment les références sont facilement oubliées.
JavaScript gère les variables non définies de manière lâche : les variables non définies créent une nouvelle variable dans l'objet global. Dans le navigateur, l'objet global est window
.
La vérité est que la fonction
foo
a oublié d'utiliser var
en interne et a accidentellement créé une variable globale. Cet exemple divulgue une simple chaîne, ce qui est inoffensif, mais il y a des choses pires.
Une autre variable globale inattendue peut être créée par this
:
Ajoutez
'use strict'
à l'en-tête de votre fichier JavaScript pour éviter ce type d'erreur. Activez l'analyse en mode strict de JavaScript pour éviter les variables globales inattendues.
Notes sur les variables globales
Même si nous avons discuté de certaines variables globales inattendues, il y a encore des déchets générés par des variables globales explicites. Ils sont définis comme non recyclables (sauf s’ils sont vides ou réaffectés). Des précautions doivent être prises en particulier lorsque des variables globales sont utilisées pour stocker et traiter temporairement de grandes quantités d'informations. Si vous devez utiliser une variable globale pour stocker une grande quantité de données, veillez à la définir sur null ou à la redéfinir après utilisation. La mise en cache est l’une des principales causes de l’augmentation de la consommation de mémoire liée aux variables globales. La mise en cache des données est destinée à être réutilisée et la taille du cache doit être limitée pour être utile. Une consommation élevée de mémoire entraîne le dépassement de la limite supérieure du cache, car le contenu mis en cache ne peut pas être récupéré.
L'utilisation de setInterval
en JavaScript est très courante. Un morceau de code courant :
Ce que cet exemple illustre : le timer associé au nœud ou aux données n'est plus nécessaire, node
l'objet peut être supprimé et toute la fonction de rappel n'est plus nécessaire. Cependant, la fonction de rappel du timer n'a toujours pas été recyclée (elle ne sera recyclée qu'une fois le timer arrêté). En parallèle, someResource
Si une grande quantité de données est stockée, elle ne peut pas être recyclée.
Dans le cas des observateurs, il est important de les supprimer explicitement dès qu'ils ne sont plus nécessaires (ou que l'objet associé devient inaccessible). L'ancien IE 6 ne peut pas gérer les références circulaires. Aujourd'hui, la plupart des navigateurs peuvent recycler les gestionnaires d'observateurs une fois que l'objet observateur devient inaccessible, même sans les supprimer explicitement.
Exemple de code d'observateur :
Notes sur les observateurs d'objets et les références circulaires
Les anciennes versions d'IE sont incapables de détecter les références circulaires entre les nœuds DOM et le code JavaScript, ce qui peut entraîner des fuites de mémoire. Aujourd'hui, les navigateurs modernes (y compris IE et Microsoft Edge) utilisent des algorithmes de récupération de place plus avancés et peuvent détecter et gérer correctement les références circulaires. En d'autres termes, il n'est pas nécessaire d'appeler removeEventListener
lors de la récupération de la mémoire du nœud.
Parfois, il est utile de sauvegarder la structure de données interne des nœuds DOM. Si vous souhaitez mettre à jour rapidement plusieurs lignes d'une table, il est logique d'enregistrer chaque ligne du DOM sous forme de dictionnaire (paire clé-valeur JSON) ou de tableau. À ce stade, il existe deux références au même élément DOM : une dans l’arborescence DOM et l’autre dans le dictionnaire. Lorsque vous déciderez de supprimer ces lignes à l'avenir, vous devrez effacer les deux références.
De plus, les problèmes de référence au sein de l'arborescence DOM ou des sous-nœuds doivent également être pris en compte. Supposons que votre code JavaScript enregistre une référence à un certain <td>
dans le tableau. Lorsque vous décidez de supprimer la table entière à l'avenir, votre intuition est que le GC recyclera d'autres nœuds à l'exception de celui enregistré <td>
. Ce n'est pas la situation réelle : ceci <td>
est un nœud enfant de la table, et l'élément enfant a une relation de référence avec l'élément parent. Parce que le code conserve une référence à <td>
, la table entière reste en mémoire. Soyez prudent lorsque vous enregistrez des références à des éléments DOM.
Les fermetures sont un aspect clé du développement JavaScript : les fonctions anonymes peuvent accéder aux variables dans la portée parent.
Exemple de code : L'extrait de code
fait une chose : chaque fois que vous appelez replaceThing
,theThing
, vous obtenez un nouvel objet contenant un grand tableau et une nouvelle fermeture (someMethod
). En même temps, la variable unused
est une fermeture qui fait référence à originalThing
(la précédente replaceThing
appelée theThing
). Vos pensées sont confuses ? La chose la plus importante est qu'une fois la portée d'une fermeture créée, elle a la même portée parent et la portée est partagée. someMethod
peut être utilisé via theThing
, someMethod
partage la portée de fermeture avec unused
, et bien que unused
ne soit jamais utilisé, sa référence à originalThing
l'oblige à rester en mémoire (empêchant son recyclage). Lorsque ce code est exécuté à plusieurs reprises, vous verrez que l'utilisation de la mémoire continue d'augmenter et que le garbage collector (GC) ne peut pas réduire l'utilisation de la mémoire. Essentiellement, une liste chaînée de fermetures a été créée, et chaque portée de fermeture comporte une référence indirecte à un grand tableau, provoquant une grave fuite de mémoire.
Le billet de blog de Meteor explique comment résoudre ce problème. Ajoutez
replaceThing
à la fin deoriginalThing = null
.
Chrome fournit un excellent ensemble d'outils pour détecter l'utilisation de la mémoire JavaScript. Deux outils importants liés à la mémoire : timeline
et profiles
.
timeline peut détecter la mémoire indésirable dans votre code. Dans cette capture d'écran, nous pouvons voir la croissance constante des objets potentiellement divulgués. À la fin de la collecte de données, l'utilisation de la mémoire est nettement plus élevée qu'au début de la collecte, et le nombre total de nœuds est également très élevé. Il existe divers signes indiquant qu'il y a des fuites de nœuds DOM dans le code.
Profiles est un outil sur lequel vous pouvez passer beaucoup de temps à vous concentrer, il peut enregistrer des instantanés, comparer différents instantanés de l'utilisation de la mémoire du code JavaScript et également enregistrer la distribution du temps. Chaque résultat contient différents types de listes, celles liées aux fuites de mémoire sont des listes récapitulatives et des listes de comparaison.
résumé (Résumé) La liste montre l'allocation et la taille totale des différents types d'objets : taille peu profonde (la taille totale de tous les objets d'un type spécifique), taille conservée (taille peu profonde plus les autres tailles d'objets qui lui sont associées). Il donne également une idée de la distance entre un objet et sa racine GC associée.
Comparez la liste de comparaison de différents instantanés pour détecter les fuites de mémoire.
Il existe essentiellement deux types de fuites : les fuites causées par une croissance périodique de la mémoire et les fuites de mémoire occasionnelles. De toute évidence, les fuites de mémoire périodiques sont faciles à détecter ; les fuites occasionnelles sont plus difficiles et sont généralement faciles à ignorer. Les fuites de mémoire occasionnelles peuvent être considérées comme un problème d'optimisation, tandis que les fuites récurrentes sont considérées comme des bogues qui doivent être résolus.
Prenons le code du document Chrome comme exemple :
Lorsque grow
est exécuté, p nœuds sont créés et insérés dans le DOM, et un énorme tableau est alloué aux variables globales. Une augmentation constante de la mémoire peut être détectée grâce aux outils mentionnés ci-dessus.
La balise timeline est efficace pour cela. Ouvrez l'exemple dans Chrome, ouvrez Dev Tools, passez à la chronologie, vérifiez la mémoire et cliquez sur le bouton d'enregistrement, puis cliquez sur le bouton The Button
sur la page. Arrêtez l'enregistrement après un certain temps et voyez les résultats :
Deux signes montrent une fuite de mémoire, Nodes (ligne verte) et tas JS (ligne bleue) dans l'image. Les nœuds croissent régulièrement et ne diminuent pas, ce qui est un signe significatif.
L'utilisation de la mémoire du tas JS augmente également régulièrement. Pas si facile à trouver à cause du ramasse-miettes. L'image montre que l'utilisation de la mémoire augmente et diminue. En fait, après chaque baisse, la taille du tas JS est plus grande qu'auparavant. En d’autres termes, même si le garbage collector continue de collecter de la mémoire, celle-ci subit toujours des fuites périodiques.
Après avoir confirmé qu'il y a une fuite de mémoire, nous trouvons la cause première.
Basculez vers l'onglet Profils des outils de développement Chrome, actualisez la page et, une fois l'actualisation de la page terminée, cliquez sur Prendre un instantané du tas pour enregistrer l'instantané comme référence. Cliquez ensuite à nouveau sur le bouton The Button
, attendez quelques secondes et enregistrez le deuxième instantané.
Sélectionnez Résumé dans le menu des filtres, sélectionnez Objets alloués entre l'instantané 1 et l'instantané 2 sur la droite, ou sélectionnez Comparaison dans le menu des filtres, et vous pourrez alors voir une liste de comparaison.
Cet exemple est facile à trouver des fuites de mémoire, regardez le (string)
Constructeur de Size Delta
, 8 Mo, 58 nouveaux objets. Le nouvel objet est alloué, mais pas libéré, occupant 8 Mo.
Si vous développez le constructeur (string)
, vous verrez de nombreuses allocations de mémoire individuelles. En sélectionnant une allocation individuelle, les mandats suivants attireront notre attention.
L'allocation que nous avons sélectionnée fait partie d'un tableau associé à la variable window
de l'objet x
. Le chemin complet depuis l'énorme objet jusqu'à la racine (window
) qui ne peut pas être recyclée est affiché ici. Nous avons trouvé la fuite potentielle et d'où elle vient.
Notre exemple est relativement simple, seul un petit nombre de nœuds DOM sont divulgués et il est facile à trouver à l'aide de l'instantané mentionné ci-dessus. Pour les sites plus grands, Chrome propose également la fonctionnalité Enregistrer les allocations de tas.
Retournez à l'onglet Profils de Chrome Dev Tools et cliquez sur Enregistrer les allocations de tas. Lorsque l'outil est en cours d'exécution, faites attention à la barre bleue en haut, qui représente l'allocation de mémoire. Il y a une grande quantité d'allocation de mémoire chaque seconde. Il fonctionne quelques secondes puis s'arrête.
Vous pouvez voir l'atout de l'outil dans l'image ci-dessus : sélectionnez une certaine chronologie et vous pouvez voir l'allocation de mémoire pendant cette période. En choisissant une chronologie aussi proche que possible du pic, la liste ci-dessous ne montre que trois constructeurs : l'un est le constructeur le plus divulgué (string)
, le suivant est l'allocation DOM associée et le dernier est le constructeur Text
(le texte contenu par le nœud feuille DOM ).
Sélectionnez un constructeur HTMLpElement
dans la liste, puis sélectionnez Allocation stack
.
Vous savez maintenant où les éléments sont alloués (grow
-> createSomeNodes
Regardez de plus près la chronologie dans l'image et constatez que le constructeur HTMLpElement
a été appelé plusieurs fois, ce qui signifie que la mémoire a été occupée et ne peut pas être utilisée. être recyclés par GC. Nous connaissons l'emplacement exact où ces objets sont attribués (createSomeNodes
). Revenons au code lui-même, voyons comment réparer la fuite de mémoire.
Dans la zone de résultats des allocations de tas, sélectionnez Allocation.
Cette vue présente une liste de fonctions liées à l'allocation de mémoire, et nous voyons immédiatement grow
et createSomeNodes
. Lorsque grow
est sélectionné, en regardant le constructeur d'objet concerné, il est clair que (string)
, HTMLpElement
et Text
sont divulgués.
Combiné aux outils mentionnés ci-dessus, vous pouvez facilement trouver des fuites de mémoire.
【Apprentissage recommandé : Tutoriel avancé javascript】
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!