Pour optimiser efficacement l'espace mémoire utilisé par votre jeu, vous devez d'abord comprendre comment la plate-forme Android mesure la mémoire et comment utiliser la télémétrie système, les API de diagnostic et les outils de profilage. Ce guide explique comment surveiller, capturer et analyser les allocations de mémoire de votre jeu conformément aux nouvelles consignes de la plate-forme.
Comprendre les métriques RSS et d'échange
Pour analyser et déboguer efficacement le comportement de la mémoire de votre jeu, vous devez comprendre les métriques techniques exactes que la plate-forme Android utilise pour l'application de la mémoire. Pour obtenir des informations générales détaillées sur la façon dont ce paramètre de télémétrie est traité et surveillé dans la nature, consultez la documentation Android Vitals – Utilisation de la mémoire (RSS anonyme + échange).
1. RSS anonyme (RssAnon)
La taille de l'ensemble résident (RSS) mesure la partie de la mémoire occupée par un processus qui est conservée dans la RAM physique de l'appareil. La RSS est divisée en mémoire sauvegardée dans un fichier et en mémoire anonyme. La métrique d'application d'Android se concentre strictement sur la RSS anonyme :
- Ce qu'elle inclut : pages de mémoire allouées directement par le processus de votre jeu qui ne sont pas liées à un fichier physique sur le stockage. Ces pages incluent des tas Java ou Kotlin, des piles d'exécution de threads et, surtout, des allocations de mémoire native (telles que des allocateurs de moteur C++ personnalisés ou des blocs de mémoire demandés à l'aide de malloc ou de new natifs et modifiés par la logique du jeu). Pour en savoir plus sur cette métrique, consultez le dictionnaire Mémoire de processus (RSS).
- Pourquoi est-ce important ? Les moteurs de jeu utilisent des pools de mémoire native massifs pour gérer la physique, le rendu et la logique. Comme ces pools ne sont pas sauvegardés par des fichiers, ils résident entièrement dans la RSS anonyme et constituent la majeure partie de l'empreinte physique de votre jeu.
2. Échange non compressé (VmSwap)
Android n'est pas compatible avec un espace d'échange traditionnel basé sur disque en raison de l'usure du stockage flash et des contraintes de latence. Il utilise plutôt zRAM (échange non compressé) :
- Ce qu'il inclut : lorsque la pression sur la RAM physique augmente, le daemon de gestion de la mémoire du noyau compresse les pages anonymes inactives et les déplace vers une partie dédiée et non compressée de la RAM physique (zRAM).
- Calcul de la métrique : le système effectue le suivi en fonction de la taille non compressée (VmSwap) pour évaluer la demande réelle de mémoire physique du jeu. Si votre jeu alloue de la mémoire et que le système l'échange avec zRAM, il est toujours comptabilisé dans l'empreinte mémoire totale de votre jeu.
3. États du processus
L'utilisation de la mémoire est répartie par états de processus dans Android Vitals. Pour les développeurs de jeux, les SDK ou jeux tiers peuvent également déclencher de manière inattendue des services perçus par l'utilisateur ou des services en arrière-plan.
- Ce qu'elle inclut : premier plan, services perceptibles, arrière-plan et mis en cache.
- Pourquoi est-ce important ? Les différents états de processus ont des impacts différents sur la gestion de la mémoire de l'OS Android. Vous ne savez peut-être pas que votre jeu s'exécute avec un état de processus sensible si l'un des SDK tiers déclenche une tâche en arrière-plan de manière involontaire. Surveillez si votre jeu s'exécute en arrière-plan
à l'aide de
RunningAppProcessInfo.
Interfaces de programmation d'application (API)
Android fournit des API système qui permettent à votre jeu de répondre de manière dynamique à la pression sur la mémoire et de capturer des diagnostics de mémoire détaillés au moment de l'exécution.
Répondre aux événements de réduction de la mémoire
Le système utilise onTrimMemory pour informer votre application des événements de cycle de vie qui
représentent une bonne opportunité pour votre application de réduire volontairement son utilisation de la mémoire
et d'éviter d'être arrêtée par le Low-Memory Killer (LMK) afin de libérer de la mémoire pour
d'autres applications.
Si le système arrête votre application en arrière-plan, l'utilisateur constate un démarrage à froid lent lors de la reprise. La réduction de l'utilisation de la mémoire en arrière-plan permet d'éviter ces arrêts en arrière-plan.
Lorsque vous répondez à des événements de réduction, libérez les allocations de mémoire volumineuses et reconstructibles qui ne sont pas nécessaires immédiatement :
Exemple : réduisez ou supprimez les bitmaps mis en cache (décodés à partir du stockage local) en réponse à
TRIM_MEMORY_UI_HIDDEN.
Kotlin
class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
override fun onTrimMemory(level: Int) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
Java
public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
public void onTrimMemory(int level) {
switch (level) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
}
ProfilingManager
Introduite dans Android 15 (niveau d'API 35), l'API ProfilingManager permet aux
applications de capturer des instantanés définis par programmation (tels que des profils de tas
, des traces système et des vidages de tas Java) directement au moment de l'exécution.
Les développeurs peuvent déclencher des captures manuellement dans des scènes spécifiques ou enregistrer des déclencheurs automatisés tels que TRIGGER_TYPE_ANOMALY pour déclencher automatiquement une capture lorsque le processus du jeu dépasse les seuils du Memory Limiter. Toutefois, les développeurs de jeux doivent tenir compte des limites critiques des moteurs de jeu modernes :
Remarque : Les moteurs de jeu modernes (tels que Unity ou Unreal) gèrent les performances d'exécution en préallouant des blocs de mémoire virtuelle massifs à partir du noyau à l'aide de mmap avec l'indicateur MAP_ANONYMOUS. Les moteurs utilisent ensuite des sous-allocateurs personnalisés (par exemple, le gestionnaire de mémoire native de Unity ou les BinnedAllocators d'Unreal) pour subdiviser et allouer des blocs de mémoire en interne.
ApplicationExitInfo
Si votre jeu est arrêté en arrière-plan ou arrêté, car il a dépassé les limites de mémoire de processus individuelles, les mécanismes standard de fichier de crash dump Java ou natif (tels que Firebase Crashlytics) n'enregistrent pas l'événement. Pour interroger et enregistrer ces
arrêts par programmation, les développeurs doivent exploiter l'API
ApplicationExitInfo au démarrage du jeu.
- Implémentation : au démarrage, appelez
ActivityManager.getHistoricalProcessExitReasons()pour récupérer les raisons de sortie des sessions récentes. - Principales raisons de sortie de la mémoire :
REASON_LOW_MEMORY: indique que le processus a été arrêté par le Low-Memory Killer (LMK) du système. Cet arrêt se produit lorsque la pression sur la mémoire à l'échelle de l'appareil est élevée et que le système d'exploitation doit récupérer de la RAM. Cette raison de sortie indique que l'empreinte en arrière-plan de votre jeu est trop importante pour coexister avec d'autres applications.REASON_MEMORY_LIMITER(Android 17 (niveau d'API 37) et versions ultérieures) : indique que le processus a été arrêté spécifiquement, car il a dépassé sa limite de mémoire cgroup (RssAnon + VmSwap) attribuée par le Memory Limiter de la plate-forme. Cet arrêt peut se produire même s'il reste suffisamment de mémoire physique sur l'appareil, ce qui signifie une violation directe des limites de processus individuelles.
Utiliser les outils disponibles
Utilisez les outils de plate-forme suivants lors du développement et de l'assurance qualité pour mesurer précisément l'utilisation de la mémoire de votre jeu.
meminfo
Cet outil collecte des statistiques sur la mémoire pour indiquer la quantité de mémoire PSS allouée et les catégories pour lesquelles elle a été utilisée.
Imprimez les meminfo statistiques de l'une des manières suivantes :
- Utilisez la commande
adb shell dumpsys meminfo package-name. - Utilisez l'appel
MemoryInfoà partir de l'API Android Debug.
La valeur PrivateDirty indique la quantité de RAM disponible dans le processus
qui ne peut pas être paginée sur le disque et qui n'est partagée avec aucun autre processus. La majeure partie de cette mémoire devient disponible pour le système lorsque ce processus est arrêté.
Tracepoints pour la mémoire
Les tracepoints pour la mémoire analysent la quantité de mémoire RSS utilisée par votre jeu. Le calcul de l'utilisation de la mémoire RSS est beaucoup plus rapide que pour la mémoire PSS. Grâce à cette rapidité, les valeurs RSS offrent une meilleure visibilité sur l'évolution de la quantité de mémoire, ce qui permet des mesures plus précises des pics d'utilisation de la mémoire. Par conséquent, il est plus facile d'identifier les pics susceptibles d'entraîner une mémoire insuffisante du jeu.
Perfetto
Perfetto est une suite d'outils permettant de collecter des informations sur les performances et la mémoire
d'un appareil, puis de les afficher dans une interface utilisateur Web. Comme il prend en charge les traces allongées de longueur variable, cela vous permet de suivre l'évolution des valeurs RSS au fil du temps. Vous pouvez également effectuer des requêtes SQL sur les données qu'il produit pour analyser les valeurs hors connexion. Activez les longues
traces à partir de l'application de traçage système. Assurez-vous que la catégorie
est activée pour la trace.memory:Memory Pour l'instrumentation de mémoire personnalisée lors du développement et
des tests, vous pouvez également utiliser l'API (bêta) heapprofd.
Inspecter RssAnon et l'échange dans Perfetto
Pour vérifier l'impact de la mémoire anonyme et de l'échange zRAM de votre jeu, chargez votre fichier de trace dans l'interface utilisateur Web à l'adresse ui.perfetto.dev et suivez ces techniques d'analyse, conçues pour des études de cas approfondies sur la mémoire (pour en savoir plus, consultez Études de cas sur l'analyse de la mémoire Perfetto) :
1. Visualiser les compteurs de mémoire sur la timeline
- Recherchez votre processus : dans la liste de navigation, recherchez le nom du package ou du processus de votre jeu.
- Développez le groupe de pistes : cliquez sur la ligne de votre processus pour développer ses pistes de thread, puis recherchez le sous-groupe nommé "Memory" (Mémoire).
- Analysez les pistes :
- mem.rss.anon (RSS anonyme) : ce graphique linéaire indique la RAM physique en temps réel occupée par les pools de mémoire non gérés de votre jeu. Surveillez cette timeline lors des chargements de scènes, des pop-up d'interface utilisateur ou des transitions de gameplay pour vérifier les pics d'allocation élevés.
- mem.swap (échange compressé ou VmSwap) : ce graphique représente la taille précompressée des blocs de mémoire déplacés vers zRAM. Une activité d'échange élevée coïncidant avec le gameplay indique que votre jeu s'exécute sur un appareil à mémoire limitée et que le système compresse activement les éléments en arrière-plan.
2. Exécuter des requêtes SQL (processeur de trace) Pour une analyse hors connexion détaillée, vous pouvez exécuter des requêtes SQL directement dans la console de l'interface utilisateur Perfetto ou utiliser la bibliothèque Python autonome du processeur de trace pour calculer les pics statistiques.
Recherchez l'allocation maximale de RSS anonyme :
SELECT max(value) / 1024 / 1024 AS max_rss_anon_mb FROM counter JOIN counter_track ON counter.track_id = counter_track.id WHERE counter_track.name = 'mem.rss.anon' AND counter_track.upid IN ( SELECT upid FROM process WHERE name = 'your.game.package.name' );Corrélez RssAnon et VmSwap à un horodatage donné :
SELECT ts, track.name AS metric_type, value / 1024 / 1024 AS size_mb FROM counter JOIN counter_track track ON counter.track_id = track.id WHERE (track.name = 'mem.rss.anon' OR track.name = 'mem.swap') AND track.upid IN ( SELECT upid FROM process WHERE name = 'your.game.package.name' ) ORDER BY ts ASC;
Pour en savoir plus sur l'inspection des fichiers de trace à l'aide d'Android Studio, consultez Inspecter les traces système : Mémoire de processus (RSS). Pour en savoir plus sur la création de scripts pour les profils de mémoire, consultez Enregistrer les allocations natives.
Heapprofd
heapprofd est un outil de suivi de mémoire intégré à Perfetto. Il peut vous aider à détecter les fuites de mémoire en indiquant où la mémoire a été allouée à l'aide de malloc. heapprofd peut être démarré à l'aide d'un script Python. Comme cet outil consomme peu de ressources, il n'affecte pas les performances comme certains autres outils tels que le débogage malloc.
bugreport
bugreport est un outil de journalisation qui vous permet de savoir si votre jeu a planté ou non en raison d'un manque de mémoire. La sortie de l'outil est beaucoup plus détaillée que celle de logcat. Il est utile pour le débogage de la mémoire, car il indique si votre jeu a planté en raison d'une mémoire insuffisante ou s'il a été arrêté par le LMK.
Pour en savoir plus, consultez Capturer et lire les rapports de bug.
Outils du moteur de jeu
Bien que les journaux au niveau de la plate-forme et la télémétrie système soient essentiels pour suivre les seuils et la conformité du système d'exploitation, les outils spécifiques au moteur de jeu vous aident à attribuer directement les allocations à vos objets de jeu, aux comportements de script et aux hiérarchies de scènes actives.
Unity
Dans un environnement Unity Engine, vous pouvez estimer de manière fiable l'espace mémoire utilisé Android Anonymous RSS + Swap au moment de l'exécution (en affichant généralement une variance inférieure à 10% par rapport aux valeurs réelles au niveau du système d'exploitation) à l'aide des outils et classes de profilage natifs de Unity.
Pour obtenir un tutoriel complet pas à pas, y compris les règles de configuration et les scripts d'exécution, consultez Vérifier la mémoire avec les outils Unity.
- API du profileur Unity : vous pouvez approximer par programmation l'espace mémoire utilisé non géré de votre jeu au moment de l'exécution en interrogeant les métriques principales du moteur :
- Utiliser la classe Profiler : suivez les allocations de mémoire totales en additionnant
les valeurs de
Profiler.GetTotalReservedMemoryLong()etProfiler.GetMonoHeapSizeLong(). - Utiliser la classe
ProfilerRecorder: surveillez les catégories de mémoire de manière dynamique. Pour établir une approximation de référence fiable, récupérez la mémoire totale réservée (sur les builds de version) ou soustrayez-en la mémoire réservée Gfx (sur les builds de développement) pour supprimer les composants de mémoire graphique sauvegardés dans un fichier.
- Utiliser la classe Profiler : suivez les allocations de mémoire totales en additionnant
les valeurs de
- Profileur de mémoire Unity : pour identifier et déboguer les fuites de mémoire hors connexion,
capturez un instantané de la mémoire et inspectez le graphique "Resident Memory on Device" (Mémoire résidente sur l'appareil)
situé dans la section "All of Memory" (Toute la mémoire). Pour calculer l'empreinte approximative, additionnez les totaux des catégories suivantes : "Untracked" (Non suivi), "Android Runtime" (Environnement d'exécution Android), "Native" (Natif) et "Managed" (Géré).
- Limitation zRAM : dans des conditions de mémoire limitées, le noyau Android peut compresser les pages de mémoire inactives dans l'espace d'échange (zRAM). Comme le profileur de mémoire Unity ne peut pas détecter les paramètres d'échange au niveau du système d'exploitation, vous pouvez constater de légères différences d'empreinte lors de scènes à forte mémoire. Comparez vos estimations avec Perfetto pour confirmer les valeurs exactes.