Limiteur de mémoire

Android 17 et versions ultérieures incluent le Memory Limiter, un service système qui surveille et limite l'utilisation de la mémoire des processus d'application à l'aide de Linux cgroup v2. Le Memory Limiter empêche les applications individuelles de consommer une quantité disproportionnée de mémoire système, ce qui réduit la pression globale sur la mémoire et empêche les processus critiques de premier plan et mis en cache d'être arrêtés de manière agressive en cas de mémoire insuffisante.

Mécanisme

Le Memory Limiter s'intègre au service Activity Manager (AMS) pour suivre les événements du cycle de vie des processus et les changements d'état. Le Memory Limiter applique les limites de mémoire à l'aide de la hiérarchie Linux kernel cgroup v2.

Pour prendre en charge le Memory Limiter, le noyau de l'appareil doit activer cgroup v2 et le contrôleur memory. Le service s'appuie spécifiquement sur les attributs suivants :

memory.high
Limite flexible. Lorsque cette limite est dépassée, le processus est limité et le noyau tente de récupérer de manière proactive la page de ce processus.
memory.swap.max
Limite la quantité maximale d'espace d'échange (tel que ZRAM) que le processus peut utiliser.

Impact sur les applications

Les applications qui fonctionnent dans les limites de leur mémoire ne sont pas affectées par le Memory Limiter.

Lorsqu'une application dépasse sa limite memory.high, le noyau supprime la mémoire propre de l'application et échange sa mémoire anonyme inactive pour que l'application reste dans la limite. En raison de cette suppression de page et de cette activité d'échange, l'application peut subir une limitation temporaire de l'exécution, mais elle continue de s'exécuter.

Dans des situations extrêmes, telles qu'une fuite de mémoire incontrôlée où l'application continue d'allouer de la mémoire anonyme une fois l'espace d'échange épuisé, l'application ne parvient pas à allouer de la mémoire et est arrêtée.

Surveillance des processus

Par défaut, le Memory Limiter surveille les processus d'application (UID >= 10000). Les processus système principaux sont exemptés afin de préserver la stabilité essentielle du système.

Le Memory Limiter attribue des limites de mémoire en fonction de l'état de visibilité du processus :

  • Processus visibles : processus hébergeant une interface utilisateur visible par l'utilisateur (telle que l'activité actuellement ciblée ou une interaction active). Étant donné que l'affichage d'une interface utilisateur nécessite un ensemble de travail plus important pour le rendu et les pipelines graphiques, les processus visibles reçoivent une limite de mémoire plus généreuse.

  • Processus non visibles : processus effectuant des tâches en arrière-plan sans présenter d'interface utilisateur (tels que les services en arrière-plan ou les récepteurs de diffusion). Comme ils ne rendent pas les composants de l'interface utilisateur, ils fonctionnent avec une limite plus restrictive.

Le tableau suivant mappe des états de processus spécifiques à des classifications de limites de mémoire :

État du processusLimite de mémoire
PERSISTENTPas de restriction
PERSISTENT_UIPas de restriction
TOPVisible
BOUND_TOPVisible
FOREGROUND_SERVICENon visible
BOUND_FOREGROUND_SERVICENon visible
IMPORTANT_FOREGROUNDVisible
IMPORTANT_BACKGROUNDNon visible
TRANSIENT_BACKGROUNDNon visible
BACKUPNon visible
SERVICENon visible
RECEIVERNon visible
TOP_SLEEPINGVisible
HEAVY_WEIGHTNon visible
HOMENon visible
LAST_ACTIVITYNon visible
CACHED_ACTIVITYEn cache
CACHED_ACTIVITY_CLIENTEn cache
CACHED_RECENTEn cache
CACHED_EMPTYEn cache

Dans l'état mis en cache, les processus sont figés, puis récupérés au maximum.

Lorsqu'un processus dépasse sa limite memory.high attribuée, le Memory Limiter détecte l'événement et peut déclencher des actions de diagnostic, telles que la capture d'un profil de mémoire ou la journalisation d'une anomalie dans statsd.

Configuration

Dans Android 17 et versions ultérieures, le Memory Limiter fournit un fichier de configuration par défaut de plate-forme standard sur la partition système, tout en prenant en charge les remplacements facultatifs spécifiques à l'appareil sur la partition vendor :

  • Configuration par défaut du système : /system/etc/memory-limiter-config.xml
  • Remplacement du fournisseur (facultatif) : /vendor/etc/memory-limiter-config.xml

Le Memory Limiter recherche d'abord /vendor/etc/memory-limiter-config.xml. S'il est présent, il utilise la configuration du fournisseur. Sinon, il revient à la configuration système par défaut dans /system/etc/memory-limiter-config.xml.

Format XML

Le fichier de configuration suit le schéma défini dans memory-limiter-config.xsd. Le fichier définit plusieurs ensembles de limites classés par mémoire disponible. Le service sélectionne l'ensemble de limites correspondant le plus élevé en fonction de la RAM disponible de l'appareil. Toutes les valeurs de mémoire sont spécifiées en mébioctets (Mio).

<MemoryLimiterConfig>
  <version>1</version>
  <configList>
    <!-- RAM minimums account for MemTotal excluding carve-outs. -->
    <limitSet>
      <!-- Limits for 16GB RAM device (MemTotal >= 13GiB): 10G/5G/5G/5G -->
      <minimumRequiredMemTotal>13312</minimumRequiredMemTotal>
      <memVisible>10240</memVisible>
      <memNotVisible>5120</memNotVisible>
      <swapVisible>5120</swapVisible>
      <swapNotVisible>5120</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 12GB RAM device (MemTotal >= 10GiB): 8G/4G/4G/4G -->
      <minimumRequiredMemTotal>10240</minimumRequiredMemTotal>
      <memVisible>8192</memVisible>
      <memNotVisible>4096</memNotVisible>
      <swapVisible>4096</swapVisible>
      <swapNotVisible>4096</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 8GB RAM device (MemTotal >= 6.5GiB): 5G/3G/3G/3G -->
      <minimumRequiredMemTotal>6656</minimumRequiredMemTotal>
      <memVisible>5120</memVisible>
      <memNotVisible>3072</memNotVisible>
      <swapVisible>3072</swapVisible>
      <swapNotVisible>3072</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 6GB RAM device (MemTotal >= 4.5GiB): 4G/2G/2G/2G -->
      <minimumRequiredMemTotal>4608</minimumRequiredMemTotal>
      <memVisible>4096</memVisible>
      <memNotVisible>2048</memNotVisible>
      <swapVisible>2048</swapVisible>
      <swapNotVisible>2048</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 4GB RAM device (MemTotal >= 3GiB): 2G/1G/1G/1G -->
      <minimumRequiredMemTotal>3072</minimumRequiredMemTotal>
      <memVisible>2048</memVisible>
      <memNotVisible>1024</memNotVisible>
      <swapVisible>1024</swapVisible>
      <swapNotVisible>1024</swapNotVisible>
    </limitSet>
  </configList>
</MemoryLimiterConfig>
version
Entier positif identifiant la version de configuration. Cette valeur doit être 1.
minimumRequiredMemTotal

Système MemTotal minimal (en Mio) requis pour que cet ensemble de limites s'applique. Le service évalue cette valeur par rapport à la mémoire totale disponible signalée par le noyau dans /proc/meminfo.

Comprendre les découpes de mémoire : la valeur MemTotal du noyau reflète la DRAM physique accessible au système d'exploitation après déduction des découpes de mémoire matérielles et micrologicielles. Les découpes réservent de la RAM pour des composants matériels spécialisés tels que le GPU, le modem de bande de base, l'ISP de la caméra, les environnements d'exécution sécurisés et les hyperviseurs, consommant généralement entre 500 Mio et plus de 1 Gio de mémoire physique.

Étant donné que MemTotal est inférieur à la DRAM physique annoncée de l'appareil, minimumRequiredMemTotal seuils incluent une marge pour ces découpes. Par exemple, une configuration ciblant les appareils de 16 Go spécifie un minimumRequiredMemTotal de 13312 (13 Gio) au lieu de 16384 (16 Gio), et un niveau pour les appareils de 12 Go spécifie 10240 (10 Gio). Cela garantit que chaque appareil correspond au niveau prévu, quelles que soient les variations de découpe au niveau de la carte.

memVisible

Limite de mémoire flexible (memory.high) en Mio appliquée aux processus visibles.

memNotVisible

Limite de mémoire flexible (memory.high) en Mio appliquée aux processus non visibles.

swapVisible

Limite d'espace d'échange (memory.swap.max) en Mio appliquée aux processus visibles.

swapNotVisible

Limite d'espace d'échange (memory.swap.max) en Mio appliquée aux processus non visibles.

Limites de plate-forme standards

Dans Android 17 et versions ultérieures, la plate-forme spécifie des limites de mémoire et d'échange standards dans /system/etc/memory-limiter-config.xml sur les niveaux de RAM physique courants :

RAM physique cible Minimum requis
MemTotal
(minimumRequiredMemTotal)
Limite de mémoire visible
(memVisible)
Limite de mémoire non visible
(memNotVisible)
Limite d'échange visible
(swapVisible)
Limite d'échange non visible
(swapNotVisible)
16 Go 13 312 Mio (13 Gio) 10 240 Mio (10 Gio) 5 120 Mio (5 Gio) 5 120 Mio (5 Gio) 5 120 Mio (5 Gio)
12 Go 10 240 Mio (10 Gio) 8 192 Mio (8 Gio) 4 096 Mio (4 Gio) 4 096 Mio (4 Gio) 4 096 Mio (4 Gio)
8 Go 6 656 Mio (6,5 Gio) 5 120 Mio (5 Gio) 3 072 Mio (3 Gio) 3 072 Mio (3 Gio) 3 072 Mio (3 Gio)
6 Go 4 608 Mio (4,5 Gio) 4 096 Mio (4 Gio) 2 048 Mio (2 Gio) 2 048 Mio (2 Gio) 2 048 Mio (2 Gio)
4 Go 3 072 Mio (3 Gio) 2 048 Mio (2 Gio) 1 024 Mio (1 Gio) 1 024 Mio (1 Gio) 1 024 Mio (1 Gio)

Principes de limite de mémoire

La configuration du Memory Limiter est conçue autour des principes de plate-forme suivants :

  • Cohérence de l'écosystème et compatibilité des applications : des limites de mémoire cohérentes sur tous les appareils garantissent des performances d'application prévisibles dans l'ensemble de l'écosystème Android. Les applications sont développées et testées par rapport aux attentes de mémoire standards. Le maintien de limites de plate-forme uniformes évite les limitations inattendues ou les arrêts prématurés.

  • Allocation proportionnelle des ressources : les limites de la plate-forme sont calibrées en fonction de la capacité de la RAM physique :

    • Processus visibles : environ 1/2 à 2/3 de la RAM physique totale allouée pour prendre en charge les charges de travail d'interface utilisateur, de composition et de rendu actives.
    • Processus non visibles : environ 1/4 à 1/3 de la RAM physique totale allouée pour les tâches en arrière-plan.
  • Applicabilité universelle : les limites s'appliquent de manière uniforme à tous les processus d'application sur l'appareil (UID >= 10000), y compris les applications système et OEM préinstallées. Le Memory Limiter ne prend pas en charge les listes d'autorisation pour exempter des applications spécifiques, ce qui garantit une gestion équitable de la mémoire dans toutes les applications.

  • Absence d'API de requête d'exécution : dans Android 17 et versions ultérieures, les applications ne peuvent pas interroger par programmation les limites de mémoire qui leur sont attribuées au moment de l'exécution. Les limites de plate-forme standards sont définies avec des marges généreuses afin que les applications bien comportées fonctionnent sans contraintes lors d'une utilisation normale.

  • Récupération du noyau et dynamique d'échange : lorsqu'un processus d'application approche de sa limite memory.high, le noyau Linux dirige la récupération de mémoire spécifiquement vers ce cgroup de processus. Cela inclut la suppression des pages sauvegardées sur fichier inactives et l'échange des pages anonymes froides vers ZRAM, ce qui maintient la fluidité du système sans imposer de pression globale sur la mémoire.

Commandes shell

La commande am memory-limiter permet aux développeurs et aux intégrateurs système d'interagir avec le service au moment de l'exécution pour le développement, les tests et le débogage :

am memory-limiter <SUB-COMMAND>

état

La sous-commande status indique l'état opérationnel et les métriques actives du Memory Limiter :

adb shell am memory-limiter status

Exemple de résultat :

Memory limiter
  enabled                  monitoring=true          ignored=none
  visibleMem=1948MB        visibleSwap=974MB
  notVisibleMem=974MB      notVisibleSwap=487MB
  started=36               watched=36               watch-failed=0
  events=0                 processes=36             process-hwm=36

Les champs clés du résultat incluent les suivants :

monitoring
Indique si le Memory Limiter surveille activement les processus.
visibleMem et notVisibleMem
Limites de mémoire absolues calculées (memory.high) actuellement appliquées pour chaque état de visibilité.
visibleSwap et notVisibleSwap
Limites d'échange absolues calculées (memory.swap.max) actuellement appliquées pour chaque état de visibilité.
events
Nombre de fois qu'un processus a dépassé sa limite attribuée.
processes
Nombre actuel de processus surveillés.

ignorer

La sous-commande ignore exclut temporairement un UID spécifique ou tous les processus de la limitation de mémoire. Cela est utile pour l'analyse comparative des performances, les tests de résistance ou le diagnostic du comportement de la mémoire :

# Ignore a specific UID
adb shell am memory-limiter ignore 10087
# Ignore all processes (temporarily disables limiting)
adb shell am memory-limiter ignore all
# Resume normal limiting operation
adb shell am memory-limiter ignore none

manuel

La sous-commande manual remplace les limites calculées pour un processus spécifique par ID de processus (PID) avec une valeur absolue personnalisée en octets. La valeur doit être un entier, mais peut inclure le suffixe MB pour indiquer Mio ou le suffixe GB pour indiquer Gio :

# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1073741824
# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1024MB
# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1GB
# Remove the manual override for PID 1234
adb shell am memory-limiter manual 1234 none

Les remplacements manuels ne s'appliquent que pendant la durée de vie de cette instance de processus spécifique. Si le processus redémarre, il revient aux limites standards en fonction de son état.

Un remplacement manuel ne peut pas dépasser les limites physiques du système.