Questions auxquelles vous devez réfléchir
En général, lorsque vous pensez avoir rencontré un problème de fuite de mémoire, vous devez réfléchir à trois questions :
Ma page occupe-t-elle trop de mémoire ? - L'outil d'affichage de la mémoire Time (vue de la mémoire Timeline) et le gestionnaire de tâches Chrome (gestionnaire de tâches Chrome) peuvent vous aider à confirmer si vous avez utilisé plus de mémoire. . La vue mémoire peut suivre le nombre de nœuds DOM , le nombre de documents document et le nombre d'événements JS d'écoute pendant le rendu de la page. En règle générale : évitez les références aux éléments DOM dont vous n'avez plus besoin, supprimez les écouteurs d'événements inutiles et soyez prudent lorsque vous stockez de gros morceaux de données que vous ne pouvez pas utiliser.
Ma page présente-t-elle une fuite de mémoire ? - ObjetSuivi des allocations (Objet touslocation tracker) vous aide à localiser les fuites en visualisant l'allocation des objets JS en temps réel. Vous pouvez également utiliser l'analyseur de tas (Heap Profiler) pour générer des instantanés de tas JS et découvrir les objets qui n'ont pas été nettoyés par le garbage collection en analysant la carte mémoire et en comparant les différences entre les instantanés.
À quelle fréquence ma page est-elle récupérée ? - Si votre page est fréquemment collectée, cela signifie qu'elle est peut-être allouée trop fréquemment. La vue de la mémoire chronologique peut vous aider à trouver des pauses intéressantes.
Terminologie et Concepts de base
Cette section est introduite dans Analyse de la mémoire Termes courants utilisés dans les outils d'analyse de la mémoire pour autres langues. Les termes et concepts présentés ici sont utilisés dans l'outil d'interface utilisateur Heap Profiler et dans la documentation associée.
Ceux-ci peuvent nous aider à nous familiariser avec la manière d'utiliser efficacement les outils d'analyse de la mémoire. Si vous avez déjà utilisé des outils d'analyse de mémoire pour des langages comme Java, .NET, etc., alors ce sera une revue.
Tailles des objets
Considérez la mémoire comme un ensemble de types de base (comme les nombres et les Chaînes) et d'objets (Tableaux associatifs). Cela pourrait ressembler au diagramme suivant représentant une série de points liés.
Un objet a deux manières d'utiliser la mémoire :
L'objet lui-même utilise directement
-
Maintenir implicitement les références à d'autres objets. Cette méthode empêchera le garbage collection (GC) de recycler automatiquement ces objets.
Lorsque vous utilisez le Heap Profiler dans DevTools (Heap Profiler, un outil utilisé pour analyser les problèmes de mémoire, sous l'onglet "Profil" de DevTools), vous pourriez être surpris de trouver des colonnes qui afficher diverses informations. Deux d'entre eux sont : Occupant directement la mémoire (taille peu profonde) et Occupant la mémoire totale (taille conservée) Alors, qu'est-ce qu'ils signifient ?
Occuper directement la mémoire (Shallow Size, excluant la mémoire occupée par l'objet référencé)
C'est la mémoire occupée par l'objet lui-même.
Un objet JavaScript typique aura une mémoire réservée pour décrire l'objet et stocker sa valeur directe. Généralement, seuls les tableaux et les chaînes occuperont directement de manière significative la mémoire (taille peu profonde). Mais les chaînes et les tableaux stockent souvent la partie principale des données dans la mémoire du moteur de rendu, exposant uniquement un petit objet wrapper dans la pile d'objets JavaScript.
La mémoire du moteur de rendu fait référence à toute la mémoire utilisée lors du processus de rendu de la page que vous analysez : JS dans les processus de travail concernés (workers) déclenchés par la page mémoire utilisée par le tas JS dans la page mémoire de la page lui-même La mémoire utilisée par le tas. Cependant, un petit objet peut indirectement occuper une grande quantité de mémoire en empêchant d'autres objets d'être automatiquement collectés par le garbage collection.
Mémoire totale occupée (Taille conservée, y compris la mémoire occupée par les objets référencés)
Une fois qu'un objet est supprimé, les objets dépendants qu'il référence ne peuvent pas être GC root (GC root) y fait référence, et la mémoire occupée par eux sera libérée. La mémoire totale occupée par un objet inclut la mémoire occupée par ces objets dépendants.
GC root est composé de contrôleurs(handles) Ces contrôleurs (qu'ils soient locaux ou globaux) est créé lorsqu'une référence est établie par la fonction intégrée(code natif) à un objet JavaScript en dehors du moteur V8. Tous ces contrôleurs peuvent être utilisés dans les racines GC (racines GC) > Handle scope et racines GC >gestionnaires globaux trouvé dans . La présentation de ces contrôleurs dans cet article peut prêter à confusion sans une compréhension approfondie de l'implémentation du navigateur. Vous n'avez pas besoin de trop vous soucier des racines et des contrôleurs GC.
Il existe de nombreuses racines internes du GC qui sont sans importance pour l'utilisateur. Du point de vue de l'application, il existe les situations suivantes :- Objet global Window (le tout dans
iframe). Il y a un champ de distance dans l'instantané du tas, qui correspond au chemin le plus court entre l'objet fenêtre et l'objet correspondant.
- Une arborescence DOM de document composée de tous les nœuds DOM que le document peut traverser. Tous les nœuds ne seront pas référencés par le JS correspondant, mais les nœuds référencés par JS seront conservés tant que le document existe.
- De nombreux objets peuvent être créés lors du
débogage du code ou dans la console DevTools (par exemple : après l'exécution d'un code dans la console).
Remarque : Nous recommandons aux utilisateurs de ne pas exécuter de code dans la console ou d'activer les points d'arrêt de débogage lors de la création d'instantanés de tas. La carte mémoire
commence par une racine, qui peut être l'objet du navigateur ou l'objet window
Node.js module . La manière dont ces objets sont récupérés de la mémoire n'est pas sous le contrôle de l'utilisateur. Global
Remarque : Les données dans les champs mémoire directe occupée et mémoire totale occupée sont exprimées en octets.
Arbre de mémoire totale occupé par les objetsNous avons déjà appris que le tas est une structure de réseau composée de divers objets interdépendants. Dans le monde numérique, cette structure est appelée ungraphe ou un graphe mémoire. Les graphiques sont composés de nœuds reliés par des arêtes, et ils sont tous étiquetés.
Nœuds ( ou Objet) Les noms de balises des nœuds sont déterminés par le constructeurLe nom de la fonction est déterminé
Bords Le nom de l'étiquette est l'attributnom
Dominateurs
Les dominateurs sont comme une structure arborescente car chaque objet a un dominateur. Le contrôleur d'un objet ne peut pas faire directement référence à l'objet qu'il domine, c'est-à-dire que la structure arborescente de l'objet dominé n'est pas un arbre couvrant dans le graphique.
Dans l'image ci-dessus :
Le nœud 1 domine le nœud 2
Nœud 2 domine les nœuds 3, 4 et 6
le nœud 3 domine le nœud 5
le nœud 5 domine le nœud 8
Le nœud 6 domine le nœud 7
Dans l'exemple ci-dessous, le nœud #3
est le joueur dominant de #10
, mais #7
est également le nœud dominant dans chaque GC vers #10
apparaît dans les chemins. Ainsi, si l’objet B apparaît dans chaque chemin depuis le nœud racine jusqu’à l’objet A, alors l’objet B est l’objet dominant de l’objet A.
Introduction à la V8
Dans cette section, nous décrirons quelques concepts liés à la mémoire qui sont liés à la Machine virtuelle JavaScript V8 (V8 VM ou VM) lié. Lors de l'analyse de la mémoire, il est utile de comprendre ces concepts afin de comprendre les instantanés de tas.
Description de l'objet JavaScript
a trois types primitifs :
Nombres (tels que 3.14159..)
Booléens (vrai ou faux)
Type de caractère (Chaînes) (comme 'Werner Heisenberg')
Ils ne référenceront pas d'autres valeurs, ce seront uniquement des nœuds feuilles ou des nœuds terminaux. Les
Les nombres sont stockés de deux manières :
entiervaleur directe de 31 bits, appelée : petits entiers (petits entiers)(SMI), ou
objets de tas, référencés comme Valeur de tas . Les valeurs de tas sont utilisées pour stocker des données qui ne conviennent pas au stockage SMI, telles que double, ou lorsqu'une valeur doit être encadrée, comme cette valeur, puis définir le valeur d'attribut.
Les données de caractères seront stockées des deux manières suivantes :
Tas de VM, Ou
dans la mémoire de rendu externe. À ce stade, un objet wrapper est créé pour accéder à l'emplacement de stockage, tel que les ressources de script et autres contenus stockés dans le package de page Web, au lieu d'être copié directement dans le tas de la VM.
Les objets JavaScript nouvellement créés se verront allouer de la mémoire sur le tas JavaScript (ou Tas VM). Ces objets sont gérés par le garbage collector de V8 et resteront en mémoire tant qu'il y aura une forte référence à eux. Les
Les objets locaux sont tous les objets qui ne sont pas dans le tas JavaScript, contrairement aux objets du tas, ils ne seront pas récupérés par V8 pendant leur cycle de vie Le gestionnaire ne peut que le faire. envelopper les références d'objet via JavaScript.
Chaîne de connexion est un objet formé par la fusion d'une paire de chaînes et est le résultat de la fusion. Les Chaînes de connexion ne sont fusionnées que lorsque cela est nécessaire. Comme une chaîne concaténée, une sous-chaîne doit être construite.
Par exemple : si vous concaténez a et b, vous obtenez la chaîne (a, b) qui sert à représenter le résultat de la concaténation. Si vous concaténez plus tard ce résultat avec d, vous obtenez une autre chaîne concaténée ((a, b), d).
Array (Arrays) - Les tableaux sont des objets avec des touches numériques. Ils sont largement utilisés dans le moteur V8 lors du stockage de grandes quantités de données. Les objets avec des paires clé-valeur comme les dictionnaires sont implémentés à l'aide de tableaux.
Un objet JavaScript typique peut être stocké dans l'un des deux types de tableau suivants :
propriétés nommées et
Éléments numériques
S'il n'y a que quelques propriétés, elles seront stockées directement dans l'objet JavaScript lui-même.
Carte - Un objet utilisé pour décrire un type d'objet et sa structure. Par exemple, des cartes seront utilisées pour décrire la structure des objets afin d'obtenir un accès rapide aux propriétés des objets
Groupes d'objets
Chaque groupe d'objets local est composé d'un ensemble d'objets interdépendants de. Par exemple, dans un sous-arbre DOM, chaque nœud peut accéder à son élément parent, à l'élément enfant suivant et à l'élément frère suivant, qui forment un graphe d'association. Notez que les éléments natifs ne sont pas représentés dans le tas JavaScript - c'est pourquoi leur taille est nulle lors de la création de leur objet d'habillage.
Chaque objet wrapper aura une référence à l'objet local, qui est utilisé pour transmettre des opérations sur ces objets locaux. Ces objets locaux ont également des références aux objets encapsulés. Mais cela ne crée pas de boucle irrécupérable, le GC est suffisamment intelligent pour identifier les objets locaux qui n'ont plus de référence à l'objet enveloppé et les libérer. Mais si un objet wrapper n'est pas publié, il conservera tous les groupes d'objets et les objets wrapper associés.
Conditions préalables et conseils utiles
Gestionnaire des tâches Chrome
Remarque : Lorsque vous utilisez Chrome pour le profilage de la mémoire, il est préférable de configurer un environnement de test propre.
Ouvrez le gestionnaire de mémoire de Chrome, observez le champ de mémoire et effectuez les opérations associées sur une page. Vous pouvez rapidement déterminer si cette opération entraînera une occupation importante de mémoire par la page. Vous pouvez trouver Memory Manager dans le menu Chrome > Outils ou en appuyant sur Shift Esc.
Après ouverture, faites un clic droit sur l'en-tête et sélectionnez l'option Mémoire utilisée par JavasScript.
Localiser les problèmes de mémoire via DevTools Timeline
La première étape pour résoudre le problème est de pouvoir prouver que le problème existe. Cela nécessite de créer un test reproductible qui sert de mesure de base du problème. Sans procédures reproductibles, les problèmes ne peuvent pas être mesurés de manière fiable. En d’autres termes, sans base de comparaison, il n’existe aucun moyen de savoir quels changements sont à l’origine du problème.
Le panneau Chronologie est très utile pour découvrir quand il y a un problème avec le programme. Il montre les moments où votre application Web ou votre site Web se charge et interagit. Tous les événements : du chargement des ressources au décodage de JavaScript, en passant par les calculs de style, les pauses de garbage collection et les repeintures de pages. Tous sont représentés sur la chronologie.
Lors de l'analyse des problèmes de mémoire, la Vue Mémoire sur le panneau de la chronologie peut être utilisée pour observer :
- Mémoire totale utilisée – L'utilisation de la mémoire a-t-elle augmenté ?
- Nombre de nœuds DOM
- Nombre de documents
- Nombre d'auditeurs d'événements enregistrés
En savoir plus sur la localisation de la mémoire lors de l'analyse de la mémoire Pour les fuites, consultez le profilage de la mémoire de Zack Grossbart avec les outils de développement Chrome
Prouvez le existence d'un problèmeLa première chose à faire est de découvrir ce qui, selon vous, peut être à l'origine du problème. Quelques actions en cas de fuite de mémoire. Il peut s'agir de tout événement qui se produit sur la page, y compris les survols de la souris, les clics ou d'autres interactions susceptibles d'entraîner une dégradation des performances sur la page.
Démarrez l'enregistrement dans le panneau de la chronologie (Ctrl E ou Cmd E) et effectuez l'action que vous souhaitez tester. Pour forcer le ramassage des ordures, cliquez sur l'icône de la corbeille (
) sur le panneau.Voici un exemple de fuite de mémoire où certains points ne sont pas récupérés :
Si après quelques tests répétés, vous voyez un graphique irrégulier (ci-dessus le panneau de mémoire) indique qu'il existe de nombreux objets éphémères dans votre programme. Et si une série d'actions ne maintient pas la mémoire dans une certaine plage et que le nombre de nœuds DOM ne revient pas au nombre du début, vous pouvez suspecter une fuite de mémoire.
Une fois que vous avez déterminé qu'il y a un problème de mémoire, vous pouvez utiliser l'
analyseur de tas sur lepanneau Profils profileur) pour localiser la source du problème. Exemple : Essayez l'exemple de croissance de la mémoire, qui peut vous aider à
entraîneranalyser efficacement les problèmes de mémoire à travers la chronologie. Recycleur de mémoire
Le collecteur de mémoire(comme celui de la V8) doit être capable de localiser quels objets sont vivants et lesquels sont Les objets considérés comme morts (déchets) sont non référençables (unrchacuncapable). Si le
Garbage Collection(GC) ne parvient pas à collecter les objets poubelles en raison d'erreurs logiques dans l'exécution de JavaScript, ces objets poubelles ne peuvent plus être recyclés. Des situations comme celle-ci finiront par rendre votre application de plus en plus lente. Par exemple, lorsque vous écrivez du code, certaines
variables et écouteurs d'événements ne sont plus utilisés, mais ils sont toujours référencés par certains codes. Tant que la référence existe toujours, l'objet référencé ne peut pas être correctement recyclé par GC. Pendant l'exécution de votre application, certains objets DOM peuvent avoir été mis à jour/supprimés. N'oubliez pas de vérifier les variables qui font référence aux objets DOM et de les définirnulles. . Vérifiez les propriétés des objets qui peuvent faire référence à d'autres objets (ou à d'autres éléments DOM). Gardez un œil sur la variable cache qui peut s'agrandir. Dans le panneau Profils, sélectionnez Prendre un instantané du tas et cliquez sur Démarrer ou appuyez sur Cmd E ou Ctrl E : L'instantané est initialement enregistré dans la mémoire du processus de rendu. Ils sont importés dans DevTools à la demande et vous pouvez les voir lorsque vous cliquez sur le bouton instantané . Lorsqu'un instantané est chargé dans DevTools et affiché, le nombre sous le titre de l'instantané indique la quantité totale de mémoire occupée par les objets JavaScript accessibles. Exemple : essayez le garbage collection dans l'exemple action pour surveiller l'utilisation de la mémoire dans le panneau Chronologie. Cliquez sur l'icône du bouton Effacer tout () pour effacer tous les instantanés : Remarque : Fermer la fenêtre DevTools ne supprime pas les instantanés collectés de la mémoire de rendu. Lorsque DevTools est rouvert, la liste d'instantanés précédente est toujours là. Rappelez-vous ce que nous avons mentionné précédemment, vous pouvez forcer GC dans DevTools lorsque vous prenez un instantané. Lorsque nous prenons un instantané, GC est exécuté automatiquement. Cliquez sur le bouton poubelle (ramassage des ordures) () dans la chronologie pour effectuer facilement le ramassage des ordures. Exemple : essayez des objets dispersés et analysez-les avec Heap Profiler. Vous pouvez voir la collection d’éléments (objets). Un instantané peut changer de vue en fonction de différentes tâches. Vous pouvez parcourir la zone de sélection dans l'image : Voici les trois vues par défaut : Résumé (résumé) - Afficher les objets par constructeur classification du nom Comparaison (Contraste) - Afficher la différence des objets entre deux instantanés ; Containment - peut être utilisé pour détecter le contenu du tas DominatorsLes La vue peut être ouverte dans le panneau Paramètres - affiche l'arborescence des dominateurs. Elle peut être utilisée pour trouver les points de croissance de la mémoire. Les attributs et les valeurs d'attribut des objets ont différents types et se distinguent automatiquement par la couleur. Chaque propriété est l'une des quatre suivantes : a:property - une propriété ordinaire indexée par le nom de , représentée par .( point )opérateur, ou [] (crochet) référence, telle que ["foo bar"]; 0:element - Passer des propriétés ordinaires avec des index numériques, référencées par [] (crochets) a:context var - propriétés au sein d'une fonction, dans le contexte de la fonction ; name Quote ; a:system prop - une propriété ajoutée par la VM JavaScript et inaccessible par le code JavaScript. L'objet nommé Ouvrez un instantané, qui est affiché par défaut en vue récapitulative, indiquant le nombre total d'objets et peut être développé pour afficher un contenu spécifique : initialement, un instantané s'ouvre dans la vue Résumé, affichantles totaux des objets, qui peuvent être développés pour afficher les instances : Le premier niveau est constitué des lignes "globales", elles montrent : Constructeur (constructeur) signifie tous les le nombre d'instances de l'objet généré par ce constructeur est affiché dans la colonne Objets Count Taille peu profonde affiche le nombre total de tailles peu profondes (occupant directement la mémoire) de l'objet généré par le constructeur correspondant Taille conservée La colonne affiche la mémoire maximale occupée par l'objet correspondant DistanceLa colonne affiche la distance la plus courte de l'objet à la racine GC Après avoir développé une ligne globale, toutes les instances d'objet seront affichées. La mémoire directe occupée et la mémoire totale occupée par chaque instance sont affichées en conséquence. Le nombre après le symbole @ est l'ID unique de l'objet. Avec lui, vous pouvez comparer différents instantanés objet par objet. Exemple : essayez cet exemple (s'ouvre dans un nouvel onglet) pour apprendre à utiliser la vue Plan. Rappelez-vous que les objets jaunes sont référencés par JavaScript, tandis que les objets rouges sont référencés par des nœuds détachés avec une couleur de fond jaune. Cette vue est utilisée pour comparer différents instantanés afin de trouver les différences entre les instantanés et de trouver des objets présentant des fuites de mémoire. Pour prouver qu'une certaine opération de l'application ne provoque pas de fuite (par exemple : une paire d'opérations et d'actions d'annulation, comme ouvrir un document puis le fermer, ne provoquera pas de fuite), vous pouvez essayer les étapes suivantes : Prenez un instantané du tas avant l'opération Effectuez une opération (faites l'action qui, selon vous, provoquera la fuite ); > Exemple : Essayez l'exemple (ouvrir dans un nouvel onglet) pour savoir comment l'utiliser. Vérifiez les vues pour localiser les fuites de mémoire. Vue de confinement Cette vue propose plusieurs points d'entrée : - Ces objets sont des objets "globaux" pour le code JavaScript
La figure suivante est un Vue de contrôle typique : Exemple : Essayez l'exemple (ouvrir dans un nouvel onglet) pour voir comment utiliser une vue de contrôle pour voir l'intérieur d'une fermeture Gestion des événements. Conseils pour les fermetures Nommer vos fonctions peut être utile pour distinguer les fonctions de fermeture dans votre instantané. Par exemple : l'exemple suivant ne nomme pas la fonction : Heap Analyzer
Prendre un instantané
Effacer les instantanés
Changer de vue d'instantané
Divisez les objets par différentes couleurs
System
n'a pas de type JavaScript correspondant. Ils sont intégrés au système d'objets JavaScript VM. La V8 place la plupart des objets intégrés et des objets JS utilisateur dans le même tas. Mais ce ne sont que des objets internes du V8. Afficher les détails
Vue récapitulative (vue récapitulative)
Vue de comparaison
La vue de contrôle peut être appelée une « vue plongeante » de la structure des objets de votre application. Il vous permet de regarder à l'intérieur des fonctions, tout comme vos objets JavaScript, à l'intérieur des objets VM, vous donnant une vue de très bas niveau de l'utilisation de la mémoire dans votre application.