Outil Memory · DevTools

Memory profiles

Avant de corriger un memory leak, apprenons à voir les objects qui vivent encore dans le heap — puis à expliquer la reference qui les retient.

Avant de commencer

Rendez-vous dans l’onglet Memory de vos DevTools

Ouvrez les DevTools, puis choisissez l’onglet Memory. C’est ici que nous allons observer ce qui vit encore dans le heap.

Chrome DevTools ouvert sur l’onglet Memory, avec Heap snapshot sélectionné parmi les types de profiling.
Sélectionnez Heap snapshot avant de prendre votre première photographie du heap.

Inventaire

1. Un heap snapshot photographie le heap

Un heap snapshot est l’inventaire des objects présents à un instant donné. Il ne raconte pas toute l’histoire du programme : il répond à la question « qu’est-ce qui est encore en mémoire, et pourquoi ? ».

Le lab The Boring Memory Leak au-dessus de Chrome DevTools ouvert sur l’onglet Memory, avec Heap snapshot sélectionné et les boutons de collecte du garbage collector entourés.
Le lab fournit le scénario ; DevTools permet ensuite d’en observer les objects dans le heap.

Première lecture

2. Le snapshot vient d’être pris

Chrome DevTools affiche un heap snapshot pris à l’instant, avec la liste des constructors et leurs tailles shallow et retained.
Le snapshot offre déjà une première vue par constructor : nombre d’objects, shallow size et retained size.

Le compteur du JS heap attire l’attention ; le snapshot permet d’identifier les constructors, les objects et les references qui composent cette mémoire.

Méthode

3. Identifiez le coupable

  1. Dans le lab des bases, cliquez Create 1 structure. Cette unique structure prend environ 70 à 80 MiB dans le JS heap.
  2. Prenez immédiatement un nouveau heap snapshot — aussi appelé profile. La structure y est déjà visible.
  3. Facultatif : filtrez sur le constructor Brol, puis sélectionnez une instance.
  4. Ouvrez son retaining path et remontez jusqu’à une root. C’est ce chemin qui explique pourquoi l’object est encore reachable.
Le lab après la création d’une structure et Chrome DevTools ouvert sur un nouveau heap snapshot, où le constructor Brol est visible parmi les entrées les plus volumineuses.
Une seule structure suffit ici à faire apparaître Brol dans le profile : le coupable est facile à isoler.

C’est une caricature : la réalité en boîte de Petri. Ici, l’object est énorme et le coupable se voit immédiatement. La pratique vous aidera ensuite à reconnaître la même logique dans les petites black widows de vos futurs codebases : moins visibles, mais tout aussi dangereuses.

Forage

4. Creusez jusqu’à l’origine

Dépliez l’entry retenue et observez Distance : c’est le nombre de references qui séparent l’object d’une root. Plus que la taille, cette distance vous donne le début du chemin à explorer.

Plus important encore : la ligne de code responsable de la création de l’object est conservée dans le snapshot. Un clic vous y amène directement.

Chrome DevTools affiche les entrées d’un heap snapshot, leurs distances depuis la root, leurs tailles retained et le panneau Retainers où apparaissent les fonctions createStructure et makeClumsyEntries.
Dans les retainers, les noms familiers — createStructure, makeClumsyEntries — indiquent où les gros objects ont été créés ou retenus.

Ce panneau permet de remonter des gros objects jusqu’à leur origine dans le code. La démonstration est délibérément voyante ; dans un vrai codebase, la même enquête révèle des coupables plus petits, mais plus dangereux.

Comparaison

5. Comparez ce qui a changé

Deux snapshots ne servent pas seulement à constater une hausse : DevTools peut comparer le second au premier et isoler les objects apparus, disparus ou dont la mémoire retenue a changé.

Chrome DevTools dans le panneau Memory, avec le menu Summary ouvert sur l’option Comparison et un sélecteur permettant de choisir le snapshot de référence.
Dans le menu affichant Summary, choisissez Comparison, puis sélectionnez le premier snapshot comme référence.
  1. Prenez un premier snapshot avant l’action : c’est votre référence.
  2. Exécutez le scénario, puis prenez un second snapshot.
  3. Dans Comparison, sélectionnez le premier snapshot pour voir ce que le scénario a réellement ajouté ou retenu.
Résultat de la comparaison de deux heap snapshots dans Chrome DevTools : la ligne array affiche 16 nouvelles entrées et une hausse de 67,1 MiB.
Sachez que cette vue existe : entre les deux snapshots, la différence est ici nette. La liste commence par les impacts les plus importants.

Exercice

6. À vous de jouer

Créez maintenant un clumsy batch : 10 000 structures de deux Brol chacune sont alors en mémoire, retenues par myClumsyArray.

Le lab The Boring Memory Leak après Batch create : myClumsyArray contient 10 000 entrées et le JS heap affiche 636 MiB, avec les boutons Batch create et Proper cleanup.
Le batch rend la reference depuis myClumsyArray très visible, sans devoir créer un object géant à la fois.
  1. Cliquez Batch create (clumsy), puis prenez un heap snapshot.
  2. Retrouvez les Brol dans le profile et observez ce qui les retient.
  3. Cliquez Proper cleanup, puis prenez un nouveau snapshot.
  4. Comparez les deux snapshots pour voir la différence.

Shallow size, c’est la place prise par cet object seul. Retained size, c’est ce que le garbage collector pourrait libérer si vous coupiez sa dernière reference : l’object lui-même, plus tous ceux qui ne sont accessibles qu’à travers lui. C’est donc la mesure de l’impact réel de cette reference.

Preuve

7. Une hausse n’est pas encore un memory leak

Pour conclure, reliez toujours un scénario, deux mesures comparables et un retaining path. Dans le lab des bases, ce chemin peut passer par currentA ; dans le batch volontairement maladroit, il passe par window.memoryLeakLab.myClumsyArray.

Un object présent dans un snapshot n’est pas forcément un problème. Il devient suspect lorsque son retaining path le garde reachable après que le scénario qui devait le libérer est terminé.