Capturer une empreinte de la mémoire

Capturez une empreinte de la mémoire pour voir quels objets de votre application consomment de la mémoire au moment de la capture et identifier les fuites de mémoire ou le comportement d'allocation de mémoire qui entraîne des saccades, des blocages et même des plantages de l'application. Il est particulièrement utile de prendre des empreintes de la mémoire après une longue session utilisateur, car elles peuvent afficher des objets qui sont toujours en mémoire alors qu'ils ne devraient plus y être.

Cette page décrit les outils fournis par Android Studio pour collecter et analyser les empreintes de la mémoire. Vous pouvez également inspecter la mémoire utilisée par votre application à partir de la ligne de commande dumpsys et également examiner les événements de récupération de mémoire dans Logcat.

Pourquoi profiler la mémoire d'une application ?

Android fournit un environnement mémoire géré. Lorsqu'il détermine que votre application n'utilise plus certains objets, le récupérateur libère la mémoire inutilisée pour la rendre disponible. La façon dont Android détecte la mémoire inutilisée est constamment améliorée, mais toutes les versions ont un point commun : à un moment donné, le système doit suspendre brièvement votre code. La plupart du temps, ces pauses sont imperceptibles. Toutefois, si votre application alloue de la mémoire plus rapidement que le système ne peut la récupérer, son fonctionnement peut être retardé pendant que le collecteur libère suffisamment de mémoire pour satisfaire la demande. Certains frames peuvent être ignorés pendant ce délai, et votre application peut sembler lente à réagir.

En cas de fuite, votre application peut monopoliser la mémoire en arrière-plan, même si elle ne présente pas de ralentissement visible. Ce comportement peut affecter les performances du reste de la mémoire du système, en imposant des événements de récupération de mémoire inutiles. Le système devra éventuellement mettre fin au processus de votre application pour récupérer la mémoire. Ensuite, lorsque l'utilisateur rouvre votre application, celle-ci doit redémarrer complètement.

Pour en savoir plus sur les pratiques de programmation permettant de réduire l'utilisation de mémoire de votre application, consultez la page Gérer la mémoire de votre application.

Présentation de l'empreinte de la mémoire

Pour capturer une empreinte de la mémoire, sélectionnez la tâche Analyze Memory Usage (Heap Dump) (Analyser l'utilisation de la mémoire (empreinte de la mémoire)) (utilisez Profiler: run 'app' as debuggable (complete data) (Profileur : exécuter "app" en tant que débogable (données complètes))) pour capturer une empreinte de la mémoire. La quantité de mémoire Java utilisée peut augmenter temporairement pendant la capture. C'est normal, car l'empreinte de la mémoire est effectuée dans le même processus que votre application et nécessite de la mémoire pour collecter les données. Une fois l'empreinte de la mémoire capturée, vous voyez les éléments suivants :

Vue de l'empreinte de la mémoire dans le profileur Android Studio.

La liste des classes affiche les informations suivantes :

  • Allocations : le nombre d'allocations dans le segment de mémoire.
  • Volume de mémoire native : la quantité totale de mémoire native utilisée par ce type d'objet (en octets). Certains objets alloués en Java sont inclus à ce volume, car Android utilise de la mémoire native pour certaines classes de framework, telles que Bitmap.

  • Taille superficielle : la quantité totale de mémoire Java utilisée par ce type d'objet (en octets).

  • Taille conservée : le volume total de mémoire conservé pour toutes les instances de cette classe (en octets).

Utilisez le menu "Heap" (Segment de mémoire) pour filtrer certains segments de mémoire :

  • App heap (default) (Segment de mémoire de l'application (par défaut)) : le segment de mémoire principal sur lequel votre application alloue de la mémoire.
  • Image heap (Segment de mémoire de l'image) : l'image de démarrage du système, qui contient les classes préchargées au démarrage. Les allocations ne sont jamais déplacées ni supprimées.
  • Zygote heap (Segment de mémoire de Zygote) : le segment de mémoire en copie sur écriture, à partir duquel les processus d'application sont dupliqués dans le système Android.

Utilisez la liste déroulante "Arrangement" (Disposition) pour choisir comment organiser les allocations :

  • Arrange by class (default) (Trier par classe (par défaut)) : pour regrouper les allocations par nom de classe.
  • Arrange by package (Trier par package) : pour regrouper les allocations par nom de package.

Utilisez la liste déroulante "Class" (Classe) pour filtrer les groupes de classes :

  • All classes (default) (Toutes les classes (par défaut)) : affiche toutes les classes, y compris celles des bibliothèques et des dépendances.
  • Show activity/fragment leaks (Afficher les fuites d'activité/de fragment) : affiche les classes qui provoquent des fuites de mémoire.
  • Show project classes (Afficher les classes de projet) : affiche uniquement les classes définies par votre projet.

Cliquez sur un nom de classe pour ouvrir le volet Instance. Chaque instance répertoriée inclut les éléments suivants :

  • Depth (Profondeur) : le plus petit nombre de sauts entre une racine GC et l'instance sélectionnée.
  • Volume de mémoire native : la mémoire native utilisée par cette instance. Cette colonne n'est visible que sur Android 7.0 et les versions ultérieures.
  • Taille superficielle : la mémoire Java utilisée par cette instance.
  • Taille conservée : l'espace mémoire dominé par cette instance (conformément aux relations de domination dans les arbres).

Cliquez sur une instance pour afficher les Instance Details (Détails de l'instance), y compris ses Fields (Champs) et ses References (Références). Les types de champs et de références courants sont des types structurés , des tableaux , et des types de données primitifs en Java. Effectuez un clic droit sur un champ ou une référence pour accéder à l'instance ou à la ligne de code source associée.

  • Fields (Champs) : affiche tous les champs de cette instance.
  • References (Références) : affiche toutes les références à l'objet mis en surbrillance dans l'onglet Instance.
Vues Instances, Fields (Champs) et References (Références) dans la fenêtre de l'outil "Heap Dump" (Empreinte de la mémoire).

Détecter les bitmaps en double

Vous pouvez également détecter les bitmaps redondants dans la vue "Heap Dump" (Empreinte de la mémoire) à partir d'Android Studio Narwhal 4.

Voici comment les trouver :

  1. Ouvrez l'onglet "Profiler" (Profileur) dans Android Studio.
  2. Cliquez sur Heap Dump (Empreinte de la mémoire) (ou Analyze Memory Usage (Analyser l'utilisation de la mémoire)), puis sur "Record" (Enregistrer) pour prendre un instantané de l'état actuel de la mémoire de votre application.
  3. Recherchez le triangle d'avertissement jaune ⚠️ dans les résultats de l'analyse. Android Studio l'utilise pour signaler les bitmaps en double stockés plusieurs fois.
    • Vous pouvez également accéder à l'en-tête du profileur, sélectionner Filter by (Filtrer par), puis choisir le paramètre Duplicate Bitmaps (Bitmaps en double).
  4. Cliquez sur une entrée signalée pour ouvrir le volet Bitmap Preview (Aperçu du bitmap), ce qui vous permet de voir exactement quelle image est en double.
  5. Utilisez cette confirmation visuelle pour identifier la logique de chargement redondante dans votre code et implémenter une meilleure stratégie de mise en cache.
Recherchez les bitmaps en double à l'aide du triangle d'avertissement jaune ⚠️.

Détecter les fuites de mémoire

Pour filtrer rapidement les classes susceptibles d'être associées à des fuites de mémoire, ouvrez la liste déroulante "Class" (Classe) et sélectionnez Show activity/fragment leaks (Afficher les fuites d'activité/de fragment). Android Studio affiche les classes qui, selon lui, indiquent des fuites de mémoire pour Activity et Fragment instances de votre application.

Pour rechercher manuellement les fuites de mémoire, parcourez les listes de classes et d'instances afin de trouver les objets dont la Taille conservée est importante. Recherchez les éventuelles fuites causées par l'un des éléments suivants :

  • Références de longue durée à Activity ou Context qui peuvent entraîner une fuite du graphique de composition Compose hébergé (tel que le ComposeView et ses sous-composables).
  • Fuite d'objets d'état Jetpack Compose (MutableState), de détenteurs d'état ou de lambdas qui capturent Context.
  • Oubli de nettoyer les écouteurs ou les observateurs dans le onDispose bloc d'un DisposableEffect.
  • Classes internes non statiques, telles qu'une Runnable, qui peuvent contenir une Activity instance.
  • Caches qui maintiennent des objets plus longtemps que nécessaire.

Lorsque vous détectez des fuites de mémoire potentielles, utilisez les onglets Fields (Champs) et References (Références) dans Instance Details (Détails de l'instance) pour accéder à l'instance ou à la ligne de code source qui vous intéresse.

Déclencher des fuites de mémoire pour les tests

Pour analyser l'utilisation de la mémoire, vous devez mettre à l'épreuve le code de votre application et essayer de forcer l'apparition des fuites de mémoire. L'une des manières de procéder consiste à exécuter votre application pendant une période prolongée avant d'inspecter le tas de mémoire. Le volume accumulé par les fuites peut les hisser au sommet des allocations du tas de mémoire. Toutefois, plus le débit de fuite est faible, plus celle-ci mettra de temps à devenir visible.

Vous pouvez également déclencher une fuite de mémoire en appliquant l'une des méthodes suivantes :

  • Faites pivoter l'appareil en mode portrait, puis en mode paysage. Répétez l'opération plusieurs fois dans différents états d'activité. La rotation de l'appareil peut souvent entraîner une fuite d'une Activity (et, par conséquent, de son arbre d'interface utilisateur Compose hébergé et des arbres d'état associés) si votre application contient une référence à Activity ou Context dans des opérations asynchrones ou des détenteurs d'état.
  • Basculez entre votre application et une autre application dans différents états d'activité. Par exemple, accédez à l'écran d'accueil, puis revenez à votre application.

Exporter et importer un enregistrement d'empreinte de la mémoire

Vous pouvez exporter et importer un fichier d'empreinte de la mémoire à partir de l'onglet Past Recordings (Enregistrements précédents) du profileur. Android Studio enregistre l'enregistrement sous forme de fichier .hprof.

Vous pouvez également utiliser un autre analyseur de fichiers .hprof, tel que jhat, Pour cela, vous devez convertir le fichier .hprof du format Android au format de fichier .hprof Java SE. Pour convertir le format de fichier, utilisez l'outil hprof-conv fourni dans le répertoire {android_sdk}/platform-tools/. Exécutez la commande hprof-conv avec deux arguments : le nom de fichier .hprof d'origine et l'emplacement où écrire le fichier .hprof converti, y compris le nouveau nom de fichier .hprof. Exemple :

hprof-conv heap-original.hprof heap-converted.hprof

Ressources supplémentaires