Terminologie liée à la mémoire

Meggin Kearney
Meggin Kearney

Cette section décrit les termes couramment 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, il peut s'agir d'un rappel.

Tailles d'objets

Considérez la mémoire comme un graphique comportant des types primitifs (comme des nombres et des chaînes) et des objets (tableaux associatifs). Il peut être représenté visuellement sous la forme d'un graphique avec un certain nombre de points interconnectés comme suit:

Représentation visuelle de la mémoire

Un objet peut contenir de la mémoire de deux manières:

  • Directement par l'objet lui-même.
  • De manière implicite, en conservant des références à d'autres objets, ce qui empêche ainsi leur suppression automatique par un récupérateur de mémoire (GC).

Lorsque vous utilisez le Profileur de tas 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 êtes probablement amené à examiner différentes colonnes d'informations. Parmi les deux qui se démarquent, la taille superficielle et la taille conservée, mais que représentent-elles ?

Taille superficielle et conservée

Taille superficielle

Il s'agit de la taille de la mémoire détenue par l'objet lui-même.

Les objets JavaScript classiques disposent d'une quantité de mémoire réservée pour leur description et pour stocker des valeurs immédiates. En règle générale, seuls les tableaux et les chaînes peuvent avoir une taille superficielle significative. Toutefois, les chaînes et les tableaux externes ont souvent leur principal espace de stockage 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 à la mémoire du processus dans laquelle une page inspectée est affichée: mémoire native + mémoire de tas de mémoire 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 GC.

Les racines de récupération de mémoire sont composées de identifiants créés (localement ou globaux) lors de la référence à partir du code natif vers un objet JavaScript en dehors de V8. Tous ces identifiants se trouvent dans un instantané de segment de mémoire sous Racines GC > Gérer le champ d'application et Racines GC > Identifiants globaux. Il peut être difficile de décrire les identifiants dans cette documentation sans avoir à détailler l'implémentation du navigateur. Vous n'avez pas à vous soucier des racines et des poignées de la récupération de mémoire.

Il existe de nombreuses racines de récupération de mémoire internes, 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 la fenêtre (dans chaque iFrame). Les instantanés de segment de mémoire contiennent un champ de distance, qui correspond au nombre de références de propriétés sur le chemin de conservation le plus court de la fenêtre.
  • Arborescence DOM du document composée de tous les nœuds DOM natifs accessibles en passant le document. Il est possible qu'ils ne disposent pas tous de wrappers JS, mais s'ils disposent de wrappers, ils seront actifs tant que le document sera actif.
  • Parfois, les objets peuvent être conservés par le contexte du débogueur et 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 claire et sans 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 façon dont cet objet racine est récupéré.

Impossible de contrôler l'objet racine

Tout ce qui n'est pas accessible depuis la racine reçoit une récupération de mémoire.

Objets conservant l'arborescence

Le tas de mémoire est un réseau d'objets interconnectés. Dans le monde mathématique, cette structure est appelée graphique ou graphe de mémoire. Un graphe est construit à partir de nœuds connectés au moyen d'arêtes, chacune ayant chacun une étiquette donnée.

  • Les nœuds (ou objets) sont libellés à l'aide du nom de la fonction constructor utilisée pour les créer.
  • Les arêtes sont libellées à l'aide des noms des propriétés.

Découvrez comment enregistrer un profil à l'aide du Profileur de tas de mémoire. Parmi les éléments accrocheurs ci-dessous, nous pouvons voir dans l'enregistrement du Profileur de tas de mémoire la distance: la distance par rapport à la racine de la récupération de mémoire. Si presque tous les objets du même type se trouvent à la même distance, et que quelques-uns sont à une plus grande distance, cela vaut la peine d'être examiné.

Distance à partir de la racine

Dominateurs

Les objets "Dominator" sont constitués d'une arborescence, car chaque objet possède exactement un dominateur. Un dominateur d'un objet peut manquer de références directes à un objet qu'il domine. En d'autres termes, l'arbre du dominateur n'est pas un arbre couvrant le graphique.

Dans le schéma 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.

Structure sous forme d'arbre dominant

Dans l'exemple ci-dessous, le nœud #3 est l'élément principal de #10, mais #7 existe également dans tous les chemins simples allant de la récupération de mémoire à #10. Par conséquent, un objet B domine un objet A si B existe dans chaque chemin simple reliant la racine à l'objet A.

Illustration de dominateur animée

Caractéristiques de V8

Lors du profilage de la mémoire, il est utile de comprendre pourquoi les instantanés de tas de mémoire se présentent d'une certaine manière. Cette section décrit certains sujets liés à la mémoire correspondant spécifiquement à la machine virtuelle JavaScript V8 (VM ou VM V8).

Représentation de l'objet JavaScript

Il existe trois types primitifs:

  • Nombres (par exemple, 3.14159..)
  • Booléens (vrai ou faux)
  • 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 d'arrêt.

Les nombres peuvent être stockés sous l'une des deux formes suivantes:

  • des valeurs entières immédiates de 31 bits appelées petits entiers (SMI) ; ou
  • des objets de segment de mémoire, appelés numéros de tas de mémoire. Les numéros de tas de mémoire permettent de stocker des valeurs qui ne correspondent pas au formulaire SMI, comme les doubles, ou lorsqu'une valeur doit être encadrée, par exemple pour définir des propriétés.

Les chaînes peuvent être stockées dans:

  • 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 à un stockage externe où, par exemple, les sources de script et d'autres contenus reçus depuis le 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 sont associés à 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. Au contraire, 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 depuis JavaScript qu'à l'aide de son objet wrapper JavaScript.

Cons string est un objet qui se compose de paires de chaînes stockées, puis jointes. Il résulte d'une concaténation. La jointure du contenu de la chaîne cons ne se produit que si nécessaire. C'est le cas, par exemple, lorsqu'une sous-chaîne d'une chaîne jointe doit être construite.

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 concaténé d avec ce résultat par la suite, vous obtenez une autre chaîne d'inconvénients ((a, b), d).

Tableaux : un tableau est un objet doté de clés numériques. Ils sont largement utilisés dans les VM V8 pour stocker de grandes quantités de données. Les ensembles de paires clé/valeur utilisés comme des dictionnaires sont sauvegardés par des tableaux.

Un objet JavaScript classique peut correspondre à l'un des deux types de tableau utilisés pour le stockage:

  • des propriétés nommées et
  • éléments numériques

Dans les cas où les propriétés sont très peu nombreuses, 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 mappages permettent de décrire des hiérarchies d'objets implicites afin d'accéder rapidement aux propriétés.

Groupes d'objets

Chaque groupe d'objets natifs est composé d'objets faisant référence les uns aux autres. Prenons l'exemple d'une sous-arborescence DOM où chaque nœud est associé à son parent ainsi qu'à l'enfant suivant et au prochain frère ou sœur, formant ainsi un graphe 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. Cependant, 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.