Cette section décrit les termes courants utilisés dans l'analyse de mémoire et s'applique à divers outils de profilage de mémoire pour différents langages.
Les termes et notions décrits ici font référence au Profileur de tas de mémoire des outils pour les développeurs Chrome. Si vous avez déjà travaillé avec Java, .NET ou un autre profileur de mémoire, cela peut être un rappel.
Tailles des objets
Considérez la mémoire comme un graphe avec des types primitifs (comme des nombres et des chaînes) et des objets (tableaux associatifs). Il peut être représenté visuellement par un graphique avec un certain nombre de points interconnectés comme suit:
Un objet peut conserver de la mémoire de deux manières:
- Directement par l'objet lui-même.
- Implicitement, en conservant des références à d'autres objets, empêchant ainsi ces objets d'être automatiquement éliminés par un récupérateur de mémoire (GC en abrégé).
Lorsque vous utilisez le profileur de segments de mémoire dans les outils de développement (un outil permettant d'examiner les problèmes de mémoire trouvés sous "Profils"), vous serez probablement amené à examiner plusieurs colonnes d'informations. Taille superficielle et Taille conservée, mais que représentent-ils ?
Taille superficielle
Il s'agit de la taille de la mémoire détenue par l'objet lui-même.
Les objets JavaScript types disposent de mémoire réservée à la description et au stockage des valeurs immédiates. En règle générale, seuls les tableaux et les chaînes peuvent avoir une taille superficielle importante. Toutefois, l'espace de stockage principal des chaînes et des tableaux externes se trouve souvent dans la mémoire du moteur de rendu, ce qui n'expose qu'un petit objet wrapper sur le tas de mémoire JavaScript.
La mémoire du moteur de rendu correspond à toute la mémoire du processus d'affichage d'une page inspectée: mémoire native + mémoire de tas JS de la page + mémoire de tas de mémoire JS de tous les nœuds de calcul dédiés démarrés par la page. Néanmoins, même un petit objet peut contenir indirectement une grande quantité de mémoire, en empêchant la suppression d'autres objets par le processus automatique de récupération de mémoire.
Taille conservée
Il s'agit de la taille de la mémoire libérée une fois l'objet lui-même supprimé, ainsi que ses objets dépendants rendus inaccessibles depuis les racines Récupération de mémoire.
Les racines GC sont constituées de handles créés (locaux ou globaux) lors de la référence à partir du code natif vers un objet JavaScript en dehors de V8. Vous trouverez tous ces identifiants dans un instantané de segment de mémoire sous Racines de récupération de mémoire > Champ d'application de la gestion et Racines de récupération de mémoire > Poignées globales. Il peut être déroutant de décrire les identifiants dans cette documentation sans examiner en détail l'implémentation du navigateur. Vous n'avez pas à vous soucier des racines de récupération de mémoire ni des poignées.
Il existe de nombreuses racines internes de récupération de mémoire, dont la plupart ne sont pas intéressantes pour les utilisateurs. Du point de vue des applications, il existe les types de racines suivants:
- Objet global de fenêtre (dans chaque iFrame). Il existe un champ de distance dans les instantanés de tas de mémoire qui correspond au nombre de références de propriété sur le chemin de conservation le plus court depuis la fenêtre.
- Arborescence DOM de document composée de tous les nœuds DOM natifs accessibles en balayant le document. Il est possible qu'ils n'aient pas tous des wrappers JS, mais s'ils en disposent, ils resteront actifs tant que le document sera actif.
- Parfois, les objets peuvent être conservés par le contexte du débogueur et par la console des outils de développement (par exemple, après l'évaluation de la console). Créez des instantanés de segments de mémoire avec une console vide et aucun point d'arrêt actif dans le débogueur.
Le graphique de mémoire commence par une racine, qui peut être l'objet window
du navigateur ou l'objet Global
d'un module Node.js. Vous ne contrôlez pas la manière dont cet objet racine est récupéré.
Tout ce qui n'est pas accessible depuis la racine obtient la récupération de mémoire.
Objets conservant l'arborescence
Le tas de mémoire est un réseau d'objets interconnectés. En mathématiques, cette structure est appelée graphique ou graphe de mémoire. Un graphe est construit à partir de nœuds reliés par des arêtes, auxquelles sont attribués des étiquettes.
- Les nœuds (ou objets) sont libellés à l'aide du nom de la fonction constructor qui a été utilisée pour les créer.
- Les arêtes sont étiquetées selon le nom des propriétés.
Découvrez comment enregistrer un profil à l'aide du Profileur de tas de mémoire. La distance, c'est-à-dire la distance depuis la racine de récupération de mémoire, fait partie des choses accrocheuses que nous pouvons voir dans l'enregistrement du Profileur de tas ci-dessous. Si presque tous les objets du même type se trouvent à la même distance, et quelques-uns à une plus grande distance, cela vaut la peine d'être examiné.
Dominateurs
Les objets "Dominateur" sont composés d'une arborescence, car chacun d'eux possède un seul dominateur. Un dominateur d'un objet peut manquer de références directes à un objet qu'il domine. En d'autres termes, l'arborescence du dominateur n'est pas un arbre couvrant l'ensemble du graphique.
Dans le diagramme ci-dessous:
- Le nœud 1 domine le nœud 2
- Le 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 dominateur de #10
, mais #7
existe également dans chaque chemin simple de la récupération de mémoire à #10
. Par conséquent, un objet B est dominateur d'un objet A si B existe dans chaque chemin d'accès simple, de la racine à l'objet A.
Caractéristiques de V8
Lors du profilage de la mémoire, il est utile de comprendre à quoi ressemblent les instantanés de tas de mémoire. Cette section décrit certains sujets liés à la mémoire qui correspondent spécifiquement à la machine virtuelle JavaScript V8 (VM V8 ou VM).
Représentation d'objets JavaScript
Il existe trois types de primitives:
- Les nombres (par exemple, 3.14159..)
- Booléens (true ou false)
- Chaînes (par exemple, "Werner Heisenberg")
Elles ne peuvent pas faire référence à d'autres valeurs et sont toujours des feuilles ou des nœuds de fin.
Les nombres peuvent être stockés comme suit:
- Des valeurs entières immédiates de 31 bits appelées petits entiers (SMI)
- objets de segment de mémoire, appelés nombres de tas. Les nombres de tas de mémoire permettent de stocker des valeurs qui ne correspondent pas au format SMI, telles que les doubles, ou lorsqu'une valeur doit être encadrée, par exemple en définissant des propriétés.
Les chaînes peuvent être stockées dans:
- le tas de mémoire de la VM ;
- en externe dans la mémoire du moteur de rendu. Un objet wrapper est créé et utilisé pour accéder au stockage externe dans lequel, par exemple, des sources de script et d'autres contenus provenant du Web sont stockés au lieu d'être copiés sur le tas de mémoire de la VM.
La mémoire des nouveaux objets JavaScript est allouée à partir d'un tas de mémoire JavaScript dédié (ou tas de mémoire de VM). Ces objets sont gérés par le récupérateur de mémoire de V8 et restent donc actifs tant qu'ils contiennent au moins une référence forte.
Les objets natifs correspondent à tout le reste qui ne se trouve pas dans le tas de mémoire JavaScript. Contrairement à l'objet de tas de mémoire, l'objet natif n'est pas géré par le récupérateur de mémoire V8 tout au long de sa durée de vie et n'est accessible qu'à partir de JavaScript à l'aide de son objet wrapper JavaScript.
Cons string est un objet constitué de paires de chaînes stockées puis jointes. Il résulte d'une concaténation. La jointure des contenus de la chaîne cons n'a lieu qu'en cas de besoin. C'est par exemple le cas lorsqu'une sous-chaîne d'une chaîne jointe doit être créée.
Par exemple, si vous concaténez a et b, vous obtenez une chaîne (a, b) qui représente le résultat de la concaténation. Si vous avez ensuite concaténé d avec ce résultat, vous obtenez une autre chaîne "cons" ((a, b), d).
Tableaux : un tableau est un objet doté de clés numériques. Ils sont largement utilisés dans la VM V8 pour stocker de grandes quantités de données. Les ensembles de paires clé/valeur utilisées comme des dictionnaires sont sauvegardés par des tableaux.
Un objet JavaScript type peut correspondre à l'un des deux types de tableaux suivants utilisés pour stocker:
- des propriétés nommées ;
- éléments numériques
Si le nombre de propriétés est très faible, elles peuvent être stockées en interne dans l'objet JavaScript lui-même.
Map : objet qui décrit le type d'objet et sa mise en page. Par exemple, les cartes servent à décrire des hiérarchies d'objets implicites pour accéder rapidement aux propriétés.
Groupes d'objets
Chaque groupe d'objets natifs est constitué d'objets qui contiennent des références mutuelles les uns par rapport aux autres. Prenons l'exemple d'une sous-arborescence DOM dans laquelle chaque nœud dispose d'un lien vers son parent et d'un lien vers le prochain enfant et le frère sœur suivant, créant ainsi un graphique connecté. Notez que les objets natifs ne sont pas représentés dans le tas de mémoire JavaScript. C'est pourquoi leur taille est nulle. À la place, des objets wrapper sont créés.
Chaque objet wrapper contient une référence à l'objet natif correspondant pour rediriger les commandes vers celui-ci. À son tour, un groupe d'objets contient des objets wrapper. Toutefois, cela ne crée pas de cycle non récupérable, car la récupération de mémoire est suffisamment intelligente pour libérer des groupes d'objets dont les wrappers ne sont plus référencés. Toutefois, si vous oubliez de libérer un seul wrapper, l'ensemble du groupe et les wrappers associés seront conservés.