Présentation de la gestion de mémoire

Android Runtime (ART) et la machine virtuelle Dalvik utilisent la pagination et la mise en correspondance de mémoire (mmapping) pour gérer la mémoire. Cela signifie que toute mémoire modifiée par une application (par exemple, en allouant de nouveaux objets ou en touchant des pages mappées) reste résidente dans la RAM et ne peut pas être paginée. Le seul moyen de libérer de la mémoire à partir d'une application consiste à libérer les références d'objet que l'application contient, afin de mettre la mémoire à la disposition du récupérateur de mémoire. À une exception près : si le système souhaite utiliser cette mémoire ailleurs, il est possible que des fichiers non modifiés (comme du code) soient paginés de la mémoire RAM.

Cette page explique comment Android gère les processus des applications et l'allocation de mémoire. Pour en savoir plus sur la gestion plus efficace de la mémoire dans votre application, consultez Gérer la mémoire de votre application.

Récupération de mémoire

Un environnement de mémoire gérée, comme l'ART ou la machine virtuelle Dalvik, assure le suivi de chaque allocation de mémoire. Une fois qu'il détecte qu'une quantité de mémoire n'est plus utilisée par le programme, il le libère sur le segment de mémoire, sans aucune intervention du programmeur. Le mécanisme de récupération de mémoire inutilisée dans un environnement de mémoire géré est appelé récupération de mémoire. La récupération de mémoire a deux objectifs : trouver des objets de données dans un programme auquel l'accès est impossible par la suite ; et récupérer les ressources utilisées par ces objets.

Le tas de mémoire d'Android est générationnel, ce qui signifie qu'il suit différents buckets d'allocations en fonction de la durée de vie et de la taille prévues d'un objet alloué. Par exemple, les objets alloués récemment appartiennent à la génération Jeune. Lorsqu'un objet reste suffisamment actif, il peut être promu à une génération plus ancienne, suivi d'une génération permanente.

Chaque génération de tas de mémoire dispose de sa propre limite supérieure pour la quantité de mémoire que les objets peuvent occuper. Chaque fois qu'une génération commence à se remplir, le système exécute un événement de récupération de mémoire dans le but de libérer de la mémoire. La durée de la récupération de mémoire dépend de la génération d'objets qu'elle collecte et du nombre d'objets actifs dans chaque génération.

Même si la récupération de mémoire peut être assez rapide, elle peut tout de même affecter les performances de votre application. En règle générale, vous ne contrôlez pas à quel moment un événement de récupération de mémoire se produit dans votre code. Le système dispose d'un ensemble de critères d'exécution pour déterminer quand effectuer la récupération de mémoire. Lorsque les critères sont remplis, le système arrête l'exécution du processus et commence à récupérer la mémoire. Si la récupération de mémoire intervient au cours d'une boucle de traitement intensive, telle qu'une animation ou pendant la lecture de musique, cela peut augmenter le temps de traitement. Cette augmentation peut pousser l'exécution du code au-delà du seuil de 16 ms recommandé pour un rendu efficace et fluide des images.

En outre, votre flux de code peut effectuer des tâches qui forcent les événements de récupération de mémoire à se produire plus souvent ou à durer plus longtemps que d'habitude. Par exemple, si vous allouez plusieurs objets dans la partie la plus interne d'une boucle pour chaque image d'une animation de combinaison alpha, vous risquez de polluer votre tas de mémoire avec de nombreux objets. Dans ce cas, le récupérateur de mémoire exécute plusieurs événements de récupération de mémoire et peut dégrader les performances de votre application.

Pour plus d'informations générales sur la récupération de mémoire, consultez Récupération de mémoire.

Partager la mémoire

Afin d'intégrer tout ce dont il a besoin en RAM, Android tente de partager les pages RAM entre les processus. Pour ce faire, il procède comme suit :

  • Chaque processus d'application est dérivé d'un processus existant appelé Zygote. Le processus Zygote commence lorsque le système démarre et charge le code et les ressources du framework courants (tels que les thèmes d'activité). Pour lancer un nouveau processus d'application, le système duplique le processus Zygote, puis charge et exécute le code de l'application dans le nouveau processus. Cette approche permet de partager la plupart des pages RAM allouées au code et aux ressources du framework entre tous les processus de l'application.
  • La plupart des données statiques sont converties en processus. Cette technique permet le partage des données entre les processus et la pagination en cas de besoin. Voici des exemples de données statiques : code Dalvik (en le plaçant dans un fichier .odex pré-associé pour le mmaping direct), ressources d'application (en concevant la table des ressources comme une structure pouvant être mise en cache et en alignant les entrées ZIP de l'APK) et des éléments de projet traditionnels tels que le code natif dans les fichiers .so.
  • À de nombreux endroits, Android partage la même RAM dynamique entre les processus à l'aide de régions de mémoire partagée explicitement allouées (avec ashmem ou gralloc). Par exemple, les surfaces des fenêtres utilisent la mémoire partagée entre l'application et le compositeur d'écran, tandis que les tampons de curseur utilisent la mémoire partagée entre le fournisseur de contenu et le client.

En raison de l'utilisation intensive de la mémoire partagée, il est nécessaire de déterminer la quantité de mémoire utilisée par votre application. Les techniques permettant de déterminer correctement l'utilisation de la mémoire de votre application sont décrites dans la section Examiner votre utilisation de la RAM.

Allouer et récupérer la mémoire de l'application

Le tas de mémoire Dalvik est limité à une seule plage de mémoire virtuelle pour chaque processus d'application. Ce champ définit la taille du tas de logique, qui peut augmenter selon les besoins, mais uniquement dans la limite définie par le système pour chaque application.

La taille logique du tas de mémoire est différente de la quantité de mémoire physique utilisée par le tas de mémoire. Lors de l'inspection du tas de mémoire de votre application, Android calcule une valeur appelée "taille de l'ensemble proportionnel" (PSS), qui prend en compte à la fois les pages propres et modifiées partagées avec d'autres processus, mais uniquement dans une quantité proportionnelle au nombre d'applications qui partagent cette mémoire RAM. Ce total (PSS) correspond à ce que le système considère comme votre empreinte de mémoire physique. Pour en savoir plus sur la valeur PSS, consultez le guide Examiner votre utilisation de la RAM.

Le tas de mémoire Dalvik ne compacte pas la taille logique du tas de mémoire, ce qui signifie qu'Android ne défragmente pas le tas de mémoire pour libérer de l'espace. Android ne peut réduire la taille du tas de mémoire logique que s'il reste de l'espace inutilisé à la fin du tas de mémoire. Toutefois, le système peut toujours réduire la mémoire physique utilisée par le tas de mémoire. Après la récupération de mémoire, Dalvik parcourt le tas de mémoire et trouve les pages inutilisées, puis les renvoie au noyau à l'aide de madvise. Ainsi, les allocations et désallocations associées à de grands segments doivent récupérer l'intégralité (ou presque) de la mémoire physique utilisée. Cependant, récupérer la mémoire de petites allocations peut s'avérer beaucoup moins efficace, car la page utilisée pour une petite allocation peut toujours être partagée avec une autre ressource qui n'a pas encore été libérée.

Limiter la mémoire de l'application

Pour maintenir un environnement multitâche fonctionnel, Android limite la taille des tas de mémoire pour chaque application. La taille exacte des tas de mémoire varie selon les appareils, en fonction de la quantité de RAM dont ils disposent. Si votre application a atteint sa capacité de segment de mémoire et tente d'allouer plus de mémoire, elle peut recevoir une OutOfMemoryError.

Dans certains cas, vous pouvez interroger le système pour déterminer exactement l'espace disponible du tas de mémoire sur l'appareil actuel, par exemple pour déterminer la quantité de données pouvant être conservées dans un cache. Dans ce cas, vous pouvez interroger le système en appelant getMemoryClass(). Cette méthode renvoie un nombre entier indiquant le nombre de mégaoctets disponibles pour le tas de votre application.

Passer d'une application à l'autre

Lorsque les utilisateurs passent d'une application à une autre, Android conserve les applications qui ne sont pas au premier plan, c'est-à-dire qui ne sont pas visibles par l'utilisateur ou qui exécutent un service de premier plan tel que la lecture de musique, dans un cache. Par exemple, lorsqu'un utilisateur lance une application pour la première fois, un processus est créé pour celle-ci. Toutefois, lorsque l'utilisateur quitte l'application, ce processus ne se ferme pas. Le système conserve le processus en cache. Si l'utilisateur revient ultérieurement dans l'application, le système réutilise le processus, ce qui accélère le changement d'application.

Si votre application dispose d'un processus mis en cache et qu'elle conserve des ressources dont elle n'a pas besoin actuellement, elle affecte les performances globales du système, même si l'utilisateur ne l'utilise pas. Lorsque le système manque de ressources telles que la mémoire, il supprime les processus en cache. Le système prend également en compte les processus qui occupent le plus de mémoire et peut les arrêter pour libérer de la RAM.

Remarque : Moins une application consomme de la mémoire dans le cache, plus elle a de chances de ne pas être arrêtée et de pouvoir reprendre rapidement. Toutefois, selon la configuration système instantanée, il est possible d'arrêter les processus mis en cache à tout moment, quelle que soit leur utilisation des ressources.

Pour en savoir plus sur la mise en cache des processus lorsqu'ils ne sont pas exécutés au premier plan et sur la manière dont Android décide de les supprimer, consultez le guide Processus et threads.

Test de contrainte de mémoire

Bien que les problèmes de contrainte de mémoire soient moins courants sur les appareils haut de gamme, ils peuvent poser problème aux utilisateurs d'appareils à faible RAM, tels que ceux qui exécutent Android (édition Go). Il est important d'essayer de reproduire cet environnement soumis à une contrainte de mémoire afin de pouvoir écrire des tests d'instrumentation pour vérifier le comportement des applications et améliorer l'expérience utilisateur sur des appareils à faible mémoire.

Test de résistance d'application

Le test de résistance d'application (stressapptest) est un test de l'interface mémoire qui permet de créer des conditions de charge élevée réalistes pour tester différentes limites de mémoire et de matériel pour votre application. Dans la mesure où il est possible de définir des limites de temps et de mémoire, vous pouvez écrire une instrumentation pour vérifier des situations réelles d'utilisation élevée de la mémoire. Par exemple, utilisez l'ensemble de commandes suivant pour transférer la bibliothèque statique dans votre système de fichiers de données, la rendre exécutable et exécuter un test de contrainte de 990 Mo pendant 20 secondes :
    adb push stressapptest /data/local/tmp/
    adb shell chmod 777 /data/local/tmp/stressapptest
    adb shell /data/local/tmp/stressapptest -s 20 -M 990

  

Consultez la documentation de stressapptest pour en savoir plus sur l'installation de l'outil, sur les arguments courants et sur la gestion des exceptions.

Observations sur stressapptest

Vous pouvez utiliser des outils tels que stressapptest pour demander des allocations de mémoire plus importantes que celles qui sont librement disponibles. Ce type de requête peut générer diverses alertes dont vous devez être conscient sur le plan du développement. Voici trois des principales alertes susceptibles d'être générées en raison d'une petite quantité de mémoire disponible :
  • SIGABRT : il s'agit d'un plantage natif fatal pour votre processus, dû à une demande d'allocations de plus grande taille que la mémoire disponible, alors que la mémoire système était déjà sous pression.
  • SIGQUIT : produit un vidage de mémoire centrale et met fin au processus lorsque votre test d'instrumentation la détecte.
  • TRIM_MEMORY_EVENTS : ces rappels sont disponibles sur Android 4.1 (niveau d'API 16) ou version ultérieure et fournissent des alertes de mémoire détaillées pour votre processus.