Sous Android 17 et versions ultérieures, la plate-forme gère la mémoire des processus système à l'aide de deux mécanismes Linux cgroup v2 complémentaires qui fonctionnent en parallèle du limiteur de mémoire général de l'application :
- Seuils de protection des daemons système (
SystemMemoryBudgetManageravecmemory.min) : les réservationsmemory.minde cgroup v2 à réglage automatique (définies sur 90 % de l'ensemble de travail résident de chaque daemon et plafonnées globalement à 10% de la RAM physique totale) protègent les daemons interactifs critiques et les processus de shell (system_server,surfaceflinger, la HAL graphiquecomposer,audioserver,cameraserver,netd,SystemUIet le lanceur d'applications d'accueil) en cas de pression sur la mémoire, en remplaçant l'épinglage de fichiers statiquesPinnerServicemlock. - Plafonds de mémoire des applications système (
<memory-budget>avecmemory.high) : les limites souplesmemory.highde cgroup v2 calibrées pour la charge de travail (généralement de 30 Mo à 190 Mo) déclarées dans les fichiers manifestes pour les applications système persistantes et en arrière-plan (Bluetooth,Nfc,CalendarProvider,Traceur,MediaProvider,:PhotoPicker,NetworkStack,AppSearchetTeleService) récupèrent de manière proactive le cache de pages de fichiers froids et le tas anonyme post-burst avant que la pression de mémoire globale ne se produise.
Le tableau suivant compare les deux mécanismes :
| Dimension | Niveaux de protection des daemons système | Plafonds de mémoire des applications système |
|---|---|---|
| Service de plate-forme | SystemMemoryBudgetManager |
MemoryLimiter |
| Contrôle cgroup v2 | memory.min seuil de protection stricte |
memory.high plafond de récupération partielle |
| Processus cibles | Démons interactifs et UI de shell essentiels pour la latence (system_server, surfaceflinger, HAL composer, audioserver, cameraserver, netd, SystemUI et l'application Home) |
Applications système persistantes et sans interface graphique, fournisseurs de contenu et services d'arrière-plan (tels que Bluetooth, Nfc, MediaProvider, NetworkStack, AppSearch et TeleService) |
| Modèle de dimensionnement | Réglage automatique au moment de l'exécution (90% de l'ensemble de travail établi, plafonné globalement à 10% de la RAM totale) sans tables de réglage par appareil | Attributs XML du fichier manifeste <memory-budget> déclaratif calibrés par niveau de RAM, état du processus et nombre de pixels de l'écran |
| Principal avantage | Évite les à-coups de l'UI interactive et les blocages de réveil en cas de pression sur la mémoire, tout en remplaçant les listes de fichiers PinnerService mlock statiques. |
Évince en continu le cache de fichiers froids à usage unique et le tas de mémoire post-burst pour laisser plus de RAM physique aux applications au premier plan et mises en cache |
Pour en savoir plus sur la syntaxe <memory-budget>, les attributs XML, les API d'exécution et la comptabilisation de la mémoire de l'OS, consultez Définir les budgets de mémoire de l'application et Comprendre et résoudre les problèmes de mémoire Android.
Protéger les daemons interactifs critiques avec SystemMemoryBudgetManager
SystemMemoryBudgetManager conserve les processus qui se trouvent directement sur le chemin interactif de l'utilisateur résidant dans la RAM physique pendant la pression de la mémoire. Plutôt que d'épingler une liste statique de fichiers APK, ODEX et VDEX en mémoire avec mlock (comme le fait PinnerService pour config_defaultPinnerServiceFiles et config_pinnerHomePinBytes), SystemMemoryBudgetManager attribue des seuils memory.min cgroup v2 dynamiques aux processus suivants :
- Tranche du système de base (
/sys/fs/cgroup/system) :system_server,surfaceflinger, HALcomposergraphique,audioserver,cameraserveretnetd, ainsi que tous les démons système ou de fournisseur natifs spécifiques au facteur de forme listés dansconfig_systemMemoryBudgetAdditionalSystemProcesses(par exemple,android.hardware.perceptionhostetauracamerahalsur les appareils XR). - Tranche d'application interactive (
/sys/fs/cgroup/apps) : processus principal du package SystemUI configuré (config_systemUi) et processus principal du titulaire du rôle d'accueil de l'utilisateur actuel.
La protection du titulaire du rôle Home en plus de config_systemUi garantit une couverture sur tous les facteurs de forme. Sur les facteurs de forme où config_systemUi est un package de compatibilité léger (comme Wear OS, où le cadran, les notifications et les cartes s'exécutent dans l'application Home), l'interface visible par l'utilisateur bénéficie toujours d'une protection complète de l'ensemble de travail. Si l'intent Home est résolu dans le package config_systemUi, le processus est protégé une fois. Seul le processus principal de chaque package (processName == packageName) reçoit un niveau de protection. Les sous-processus auxiliaires (tels que :screenshot) ne consomment donc pas de budget de protection.
Règle d'autoréglage et délégation de cgroup à trois niveaux
Étant donné que l'ensemble de travail d'un daemon interactif varie considérablement selon les facteurs de forme et les capacités de RAM (par exemple, SystemUI utilise environ 45 Mo sur une montre connectée, 60 Mo sur un téléphone de 6 Go et 180 Mo sur un téléphone de 16 Go), les tables de conversion statiques par appareil sont fragiles. À la place, SystemMemoryBudgetManager dérive les planchers directement de l'empreinte résidente de chaque processus à l'aide de trois constantes :
| Paramètre de règle | Valeur par défaut | Comportement |
|---|---|---|
| Prix plancher par processus | 90% de la mémoire stable memory.current moins le cache de fichiers non mappés |
Protège le tas anonyme, la slab et le code mappé chaud, tout en laissant les 10% de pages les plus froides récupérables, de sorte que les pages véritablement froides puissent toujours être supprimées. Le cache de fichiers non mappés (max(0, file - shmem - file_mapped) de memory.stat) est soustrait avant d'appliquer le ratio de 90% afin que le cache d'analyse des packages de démarrage ou les lectures d'assets ponctuelles n'augmentent pas les seuils. |
| Limite de RAM globale | 10% de la RAM totale (config_systemMemoryBudgetLowKb) |
Correspond à la protection PinnerService config_pinnerMaxPinnedMemoryPercentage. Si la somme des prix planchers à 90% dépasse 10% de MemTotal, chaque prix plancher est réduit proportionnellement du même facteur. |
| Fréquence d'actualisation | 1, 4, 16 et 64 s après le démarrage, puis toutes les 30 min | Une série de stabilisation géométrique converge sur l'ensemble de travail post-démarrage de chaque daemon sans coder en dur les délais de préchauffage par appareil, et une actualisation de l'état stable de 30 minutes suit la croissance et la réduction progressives de l'ensemble de travail pendant la durée de vie de l'appareil. |
Dans le contrôleur de mémoire Linux cgroup v2 (mm/memcontrol.c:effective_protection), la protection memory.min effective d'un cgroup feuille est limitée par la protection memory.min configurée sur ses cgroups parents et ancêtres. La définition de memory.min uniquement sur un répertoire feuille pid_<pid> alors que uid_<uid>/memory.min est défini sur 0 n'offre aucune protection efficace.
À l'inverse, définir un memory.min fixe uniquement sur la tranche /sys/fs/cgroup/system supérieure répartit la protection non réclamée entre tous les UID et processus enfants de manière proportionnelle (memory_recursiveprot), ce qui permet aux processus de diagnostic ou secondaires partageant uid_1000 (android.uid.system) de siphonner la protection de surfaceflinger et audioserver.
Pour fournir une isolation exacte par daemon, SystemMemoryBudgetManager écrit la protection sur les trois niveaux de la hiérarchie cgroup v2 à chaque actualisation, comme illustré dans la figure 1 :
- Nœud de processus feuille (
pid_<pid>/memory.min) : défini sur le seuil de l'ensemble de travail (mis à l'échelle) de 90% du processus. - Nœud UID (
uid_<uid>/memory.min) : défini sur la somme exacte de ses planchers de feuillespid_*gérés, de sorte que les processus non gérés partageant cet UID (comme d'autres daemonsAID_SYSTEMou packagessharedUserId="android.uid.system") ontmemory.min = 0et ne peuvent pas demander de protection. - Tranche de niveau supérieur (
/sys/fs/cgroup/system/memory.minet/sys/fs/cgroup/apps/memory.min) : définie sur la somme exacte de ses enfantsuid_*. Toutes les valeursmemory.lowobsolètes sur les nœuds gérés sont effacées et remplacées par0, et tous les répertoiresuid_*qui étaient gérés lors d'une actualisation précédente (comme un lanceur d'applications d'accueil précédent après que l'utilisateur a changé de lanceur d'applications) sont réinitialisés sur0.
Figure 1. Délégation de plancher de protection à trois niveaux entre les tranches de cgroup.
Pourquoi memory.min est utilisé à la place de memory.low sur les noyaux MGLRU
Sous MGLRU (Multi-Generational LRU, le moteur de récupération de page par défaut sur les kernels Android 6.6 et versions ultérieures), memory.low et memory.min se comportent différemment dans mm/vmscan.c:shrink_one :
memory.low(report en un seul passage) : un cgroup dont l'utilisation est inférieure àmemory.lowest déplacé à la fin de la liste LRU memcg (MEMCG_LRU_TAIL) et ignoré pour un seul passage de récupération. Lors du prochain passage sous une pression de mémoire soutenue, le noyau récupère entièrement le cgroup et consigne un événementMEMCG_LOW. Lors de tests de résistance sur des appareils physiques avec un processus anonyme gourmand en mémoire qui atteint 85% de la RAM physique,memory.lowa permis àSystemUIsur une montre connectée de passer d'environ 45 Mo à environ 5 à 10 Mo, et àaudioserversur un appareil pliable de 16 Go de passer de 15 Mo à 2 Mo, contre un plancher d'environ 14 Mo.memory.min(seuil de protection strict) : un cgroup dont l'utilisation est inférieure àmemory.minest ignoré par la récupération globale. Comme illustré dans la figure 2, sous la même charge de travail de 85% de pression de RAM,memory.minne supprime que les 10% les plus froids de chaque daemon et maintientSystemUI,surfaceflingeretaudioserverà leur niveau de 90 %, sans aucun défaut de page anonyme ni augmentation des arrêts de processuslmkd.
memory.min fournit une réservation physique stricte correspondant à la protection de l'épinglage PinnerService mlock qu'il remplace, avec l'avantage que le LRU du noyau (plutôt qu'une liste de fichiers statique) choisit les pages de code, de tas et de graphiques actives qui restent résidentes.
Figure 2. Conservation de la plage de travail du daemon en cas de sollicitation croissante de la mémoire.
Préchauffage au démarrage et récupération du cache de fichiers non mappés après le démarrage
Le remplacement du mlock permanent PinnerService de config_defaultPinnerServiceFiles nécessite de coordonner deux comportements au démarrage :
- Préchauffage ponctuel du démarrage en arrière-plan (
PinnerService) : ignorerPinnerServicemlocksans préchauffer le cache de la page déplace environ 95 Mo de lectures de disque APK, ODEX et VDEXSystemUIvers le chemin de démarrage synchrone au démarrage deSystemUI(OnBootPhase_550), ce qui ajoute 300 à 450 ms à la fin du démarrage. L'appel demadvise(MADV_WILLNEED)n'est pas suffisant, carforce_page_cache_radans le noyau limite un seul appelMADV_WILLNEEDà une fenêtre de lecture anticipée (généralement de 128 Ko à 2 Mo). Au lieu de cela, lorsqueSystemMemoryBudgetManager.shouldSkipDefaultSystemPinningest actif,PinnerServicemappe chaque fichier deconfig_defaultPinnerServiceFilessur un thread d'arrière-plan pendantonStart, remplit de manière synchrone le fichier dans le cache de la page avecOs.mlock, et appelle immédiatementOs.munlock,munmapetclose. Cela permet de préserver le temps de démarrage rapide tout en ne laissant aucun octet verrouillé de manière permanente, de sorte que les pages de démarrage à froid puissent être supprimées ultérieurement. - Exclusion et récupération du cache d'analyse du package de démarrage
system_server: au démarrage,PackageManagerServiceanalyse les APK installés, laissant 1,5 à 2,5 Go de cache de pages de fichiers non mappés froids imputés au cgroupsystem_server. S'il est inclus dans le calculmemory.mindesystem_server, ce cache de démarrage à froid sature la limite globale de 10% de RAM et réduit proportionnellement les seuils de protection desurfaceflinger,SystemUIetaudioserver. Pour éviter le manque de capacité,SystemMemoryBudgetManagerlitmemory.statpoursystem_server, calcule le cache de fichiers non mappés en tant quemax(0, file - shmem - file_mapped)et soustrait ce cache non mappé dememory.currentavant de calculer le seuil de 90 %, comme illustré dans la figure 3. De plus, 10 secondes aprèsPHASE_BOOT_COMPLETED, le gestionnaire effectue une écriture unique de<unmapped_file> swappiness=0sur le nœudmemory.reclaimdesystem_serversur Linux 6.2 et les noyaux plus récents pour supprimer de manière proactive le cache d'analyse de package froid sans échanger de tas anonyme ni supprimer les pages DEX et de code mappées. Sur les anciens noyaux (commeandroid14-6.1) qui rejettentswappiness=0avecEINVAL, le gestionnaire ignore l'écriturememory.reclaimau lieu de revenir à une écriture de récupération non qualifiée qui échangerait le tas anonyme au démarrage.
Figure 3. Convergence du plancher de protection et récupération du cache après le démarrage.
Exigences du kernel et overlays de configuration de l'appareil
SystemMemoryBudgetManager s'appuie sur mem_cgroup_calculate_protection dans le noyau Linux pour propager memory.min entre les cgroups imbriqués. Les kernels Android courants android15-6.6 et android16-6.12 incluent une compatibilité complète avec la protection imbriquée MGLRU. Les appareils exécutant android14-6.1 avec MGLRU activé nécessitent trois rétroportages en amont dans mm/vmscan.c et mm/memcontrol.c afin que les cgroups de tranche intermédiaire et d'UID vides propagent la protection aux cgroups de processus feuille :
30d77b7eef01(mm/mglru: fix ineffective protection calculation)8aa420617918(mm/mglru: respect min_ttl_ms with memcgs)adb8213014b2(mm: memcg: fix stale protection of reclaim target memcg)
Les OEM peuvent personnaliser ou désactiver SystemMemoryBudgetManager à l'aide des superpositions de ressources de framework suivantes :
config_systemMemoryBudgetLowKb(entier, Ko) :0(par défaut) limite la somme de tous les seuilsmemory.minà 10% de la RAM physique totale. Une valeur positive définit un plafond explicite en Ko, et une valeur négative désactiveSystemMemoryBudgetManagersur l'appareil (en conservant l'ancien épinglagemlockstatiquePinnerService).config_systemMemoryBudgetHighKb(entier, Ko) :0(par défaut) laisse/sys/fs/cgroup/system/memory.highàmax. Une valeur positive définit un plafond de tranche supérieurememory.high(qui doit être supérieur ou égal au plancher).config_systemMemoryBudgetAdditionalSystemProcesses(string-array) : liste les noms de processus de daemon système ou de fournisseur natifs supplémentaires essentiels à la latence dans/sys/fs/cgroup/system(correspondant à/proc/<pid>/commou au nom de base deargv[0]dans/proc/<pid>/cmdlinepour les UID système et de fournisseur sous10000) à protéger avec des seuils de 90% dememory.min, en plus des six daemons système par défaut. Utilisez cette superposition plutôt que de définirmemory.mindansinit.rc, carSystemMemoryBudgetManagerrecalculeuid_<uid>/memory.mincomme la somme de ses processus feuilles gérés et efface les nœudsuid_*non gérés à chaque actualisation. Par exemple, un appareil XR peut protéger ses démons HAL de suivi spatial et de caméra avec la superposition suivante :<string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses"> <item>android.hardware.perceptionhost</item> <item>auracamerahal</item> </string-array>config_systemUi(chaîne) : nom du package de l'application SystemUI de la plate-forme, résolu avecMATCH_SYSTEM_ONLY.
Pour inspecter les limites SystemMemoryBudgetManager et les nœuds cgroup actifs sur un appareil, exécutez la commande suivante :
# Inspect policy state, resolved PIDs, and applied floors in dumpsys meminfo adb shell dumpsys meminfo | sed -n '/System Memory Budgets/,$p'# Inspect raw cgroup v2 memory.min nodes across the system and apps slices adb shell head -n 1 \ /sys/fs/cgroup/system/memory.min \ /sys/fs/cgroup/system/uid_*/memory.min \ /sys/fs/cgroup/system/uid_*/pid_*/memory.min \ /sys/fs/cgroup/apps/memory.min \ /sys/fs/cgroup/apps/uid_*/memory.min \ /sys/fs/cgroup/apps/uid_*/pid_*/memory.min
Limiter les applications système persistantes et en arrière-plan avec des budgets de mémoire
Bien que SystemMemoryBudgetManager protège les daemons interactifs de la récupération à l'aide des seuils memory.min, de nombreuses autres applications système et services de plate-forme privilégiés s'exécutent en arrière-plan pendant de longues périodes en tant que processus PERSISTENT, services de premier plan liés ou fournisseurs de contenu fréquemment interrogés.
Sans plafond de mémoire explicite, ces processus ne sont pas limités tant qu'ils n'atteignent pas les limites de plusieurs gigaoctets du limiteur de mémoire (par exemple, 4 Go à 10 Go pour les applications visibles et 2 Go à 5 Go pour les applications en arrière-plan) ou que la pression de la mémoire globale ne déclenche pas l'activité kswapd et le low-memory killer (lmkd) à l'échelle du système.
S'ils ne sont pas limités, les processus système en arrière-plan accumulent souvent de grandes quantités de mémoire froide qui ne fait pas partie de leur ensemble de travail actif :
- Cache de page de fichier illimité : les services qui indexent les contenus multimédias, effectuent des recherches dans les bases de données ou synchronisent les tables d'agenda et de fournisseur (telles que
AppSearch,MediaProvideretCalendarProvider) mappent ou diffusent des fichiers volumineux sur le disque. Sans plafondmemory.highde cgroup, les pages propres sauvegardées par des fichiers et issues de lectures ponctuelles restent indéfiniment imputées au cgroup du processus, ce qui consomme des centaines de mégaoctets de RAM physique. - Conservation du tas anonyme après une activité intense : les services qui connaissent des pics d'activité temporaires, tels que les pics de découverte audio et BLE
Bluetooth, la validation TLS du portail captifNetworkStackou les assistants d'interface utilisateur passant en arrière-plan, peuvent conserver des pages de tas anonymes froides longtemps après la fin de l'activité intense.
L'application de déclarations <memory-budget> calibrées (limites souples memory.high, généralement de 30 Mo à 190 Mo) aux applications système offre deux avantages principaux :
- Récupération de page proactive et localisée : lorsqu'un processus système dépasse son budget
memory.highconfiguré, le noyau Linux dirige la récupération de page spécifiquement vers ce cgroup, en supprimant d'abord les pages froides sauvegardées par des fichiers et en compressant les pages anonymes inactives vers ZRAM sans attendre la pression globale de la mémoire de l'appareil. - Plus de RAM disponible pour les applications au premier plan et le pool d'applications mises en cache : la limitation des caches de fichiers froids dans les services système toujours activés permet de récupérer des dizaines à des centaines de mégaoctets de RAM physique sur l'appareil, ce qui réduit les à-coups de l'interface utilisateur, diminue la pression de lancement de l'appareil photo et maintient plus d'applications utilisateur actives dans l'état mis en cache.
Configurer les budgets de mémoire des applications système
Les applications système utilisent la même syntaxe de fichier manifeste <memory-budget>, la même mise en cascade de l'état du processus, la même mise à l'échelle de la densité d'affichage et la même règle de résolution de la dernière clause correspondante que les applications générales. Pour obtenir la liste complète des attributs, consultez la documentation de référence sur les attributs XML.
Les OEM peuvent déclarer des éléments <memory-budget> directement dans le fichier AndroidManifest.xml d'une application système ou les fusionner à partir d'un fichier XML distinct à l'aide de la propriété additional_manifests dans Android.bp. Par convention, l'AOSP stocke les budgets des applications système de référence dans des fichiers *-budget.xml autonomes (soit dans frameworks/base/core/memory_budgets/ pour les packages de la plate-forme de base, soit avec des modules individuels tels que packages/modules/NetworkStack/memory_budgets/, packages/modules/AppSearch/apk/ et packages/services/Telephony/) et les associe à additional_manifests :
android_app {
name: "TeleService",
manifest: "AndroidManifest.xml",
additional_manifests: ["Telephony-budget.xml"],
// ...
}
Champ d'application au niveau du package (UID) ou au niveau du processus (PID)
Le fait de déclarer un budget de référence au niveau du package (<application>) ou un budget multiprocessus (<processes><process>) détermine si le noyau applique memory.high au niveau du cgroup UID (/sys/fs/cgroup/uid_<uid>/memory.high) ou au niveau du cgroup par PID (/sys/fs/cgroup/uid_<uid>/pid_<pid>/memory.high), comme illustré dans la figure 4 :
- Budget au niveau du package (
<application>) : à utiliser lorsque l'APK exécute un seul processus sous son propre UID dédié (par exemple,Bluetooth,Nfc,NetworkStack,CalendarProvider,TraceurouAppSearch). - Budget au niveau du processus (
<processes><process>) : à utiliser lorsque l'APK partageandroid:sharedUserId(par exemple,android.uid.phoneavec l'UID1001ouandroid.uid.systemavec l'UID1000) avec d'autres packages système afin que les autres processus partageant cet UID ne soient pas limités ensemble, ou lorsqu'un seul APK exécute plusieurs processus avec des rôles de mémoire distincts (par exemple, le daemonMediaProvideren arrière-plan et le sous-processus d'UI:PhotoPicker).
Figure 4. Plafonds de mémoire au niveau de l'UID et au niveau du PID.
L'exemple suivant montre MediaProvider-budget.xml, qui limite les budgets par niveau de RAM au processus MediaProvider par défaut (<process>) et un budget mis à l'échelle de l'affichage au processus d'interface utilisateur :PhotoPicker. Chaque attribut android:maxDeviceRamMb correspond aux appareils dont la RAM physique totale (MemTotal) est inférieure ou égale au seuil spécifié en Mo (ou s'applique à n'importe quelle capacité de RAM lorsqu'il est omis). Les clauses sont classées de la valeur par défaut sans contrainte au seuil maxDeviceRamMb le plus bas, de sorte que la règle de résolution de la dernière clause correspondante sélectionne le niveau applicable le plus strict :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.providers.media.module">
<uses-sdk android:targetSdkVersion="10000" />
<application>
<processes>
<process>
<memory-budget
android:maxMb="85" />
<memory-budget
android:maxMb="75"
android:maxDeviceRamMb="12288" />
<memory-budget
android:maxMb="60"
android:maxDeviceRamMb="8192" />
</process>
<process android:process=":PhotoPicker">
<memory-budget
android:maxMb="85"
android:additionalBytesPerDisplayPixel="25" />
</process>
</processes>
</application>
</manifest>
Comment les budgets de référence AOSP ont été calibrés sur les appareils Google Pixel
Les budgets de mémoire de l'application système de référence dans l'AOSP ont été calibrés sur les appareils Google Pixel, avec une RAM physique de 6 Go à 16 Go (y compris les téléphones, les pliables et les tablettes de 6 Go, 8 Go, 12 Go et 16 Go) et des résolutions d'écran allant d'environ 1,2 MP (720 x 1 600) à environ 4,5 MP (2 076 x 2 152).
La calibration a combiné deux sources de données complémentaires : la télémétrie de champ cgroup v2 à l'échelle du parc et les balayages memory.high d'appareils physiques contrôlés sur les parcours utilisateur critiques (CUJ).
Analyse de la télémétrie cgroup v2 à l'échelle du parc
Des instantanés cgroup v2 périodiques ont été collectés sur l'ensemble du parc d'appareils et segmentés par niveau de RAM physique (≤ 8 Go, 12 Go et ≥ 16 Go) et par bucket d'état de processus (foreground, perceptible, background et cached). Six principes ont guidé l'analyse statistique :
- Décomposer
memory.currenten ensemble de travail et en cache de fichiers récupérable : étant donné quememory.highrégit la charge de cgroup résidente (memory.current), chaque instantané a été décomposé à l'aide dememory.staten tas anonyme (anon), en pages anonymes échangées (swap) et en cache de pages basé sur des fichiers (file), comme illustré à la figure 5. La demande anonyme incompressible totale (anon + swapau P99) établit le plancher minimal de l'ensemble de travail pour le budget afin que le processus ne risque pas d'épuiser la ZRAM, tandis qu'un écart important entrememory.currentau P99 etanon + swapau P99 indique un cache de pages de fichiers quememory.highpeut récupérer, à condition que ce cache ne soit pas mappé et qu'il soit froid. - Prendre en compte la mémoire que
memory.highne peut pas récupérer : la mémoire partagée et les fichiers ne sont pas tous des caches froids. Les pages verrouillées (unevictabledansmemory.stat, comme la mémoire partagée verrouillée avecmlockou la RAM d'invité verrouillée d'une machine virtuelle dont les processus hôtes sont facturés au cgroup de l'application) définissent un plancher strict sous le budget, et les pages mappées (file_mapped) font souvent partie de l'ensemble de travail actif. Vérifiezunevictable,shmemetfile_mappedavant de traiter la mémoire de fichiers comme récupérable. Par exemple, environ 100 Mo de mémoireAppSearchsont verrouillés et ne peuvent pas être supprimés. Son budget couvre donc ce seuil, ainsi que son ensemble de travail d'hôte actif, avec une marge de croissance pour la région verrouillée. - Filtrer la dilution de l'état inactif : les fournisseurs de contenu (
CalendarProvider) et les outils pour les développeurs à la demande (Traceur) passent 70% à 99,9% des instantanés de champ inactifs à environ 1 Mo à 3 Mo. Le calcul des centiles bruts sur tous les instantanés dilue considérablement les estimations des 95e et 99e centiles. À la place, les centiles et les intervalles de confiance ont été calculés sur des instantanés actifs non dilués (en filtrant les instantanésCACHEDet les instantanés de référence inactifs). - Utiliser des intervalles de confiance regroupés par appareil : étant donné qu'un seul appareil fournit de nombreux instantanés périodiques au fil du temps, le fait de traiter les instantanés comme des échantillons indépendants sous-estime l'incertitude de la queue de distribution. Toutes les estimations de l'ensemble de travail P95 et P99 ont été évaluées à l'aide d'intervalles de confiance prudents de 95 % regroupés par appareil (en utilisant le nombre d'appareils distincts
N_devcomme taille d'échantillon effective). - Prendre en compte les pics temporaires avec
memory.peak: l'échantillonnage périodique peut manquer des pics d'allocation élevée de courte durée (comme la validation TLS du portail captifNetworkStacket les vérifications DNS simultanées, ou l'initialisation A2DP et BLEBluetooth). Pour les services en rafale, les budgets ont été dimensionnés pour couvrir le 90e au 95e centile des pics d'utilisation dememory.peakdu noyau sur l'ensemble des niveaux de RAM. - Vérifiez qu'un niveau de RAM reflète la RAM, et non la génération de l'appareil : les appareils plus récents ont tendance à disposer de plus de RAM et de fonctionnalités. Par conséquent, un ensemble de travail qui augmente avec le niveau de RAM peut en réalité augmenter avec les fonctionnalités. Avant d'ajouter un niveau
maxDeviceRamMb, segmentez les données par modèle d'appareil. Par exemple,com.android.phoneatteint le même P99anon + swap(environ 85 à 90 Mo) sur les téléphones de 12 Go et 16 Go de la même génération, tandis qu'un appareil pliable plus ancien de 12 Go utilise 40 Mo. Son budget utilise donc une seule valeur sans niveau de RAM, de sorte qu'un appareil doté de moins de RAM qui intègre les nouvelles fonctionnalités de téléphonie ne soit pas sous-budgétisé.
Figure 5. Ensemble de travail par rapport au cache de fichiers récupérable.
CUJ sur appareil physique et balayages de mise à l'échelle de l'affichage
Pour valider les budgets dérivés de la télémétrie et calibrer les budgets proportionnels à l'affichage (comme :PhotoPicker) et les budgets d'arrière-plan spécifiques à l'état (comme Traceur), des balayages memory.high en direct ont été exécutés sur des appareils physiques de 6 Go à 16 Go de RAM :
- Trouver le point d'inflexion zéro défaut : lors de l'exécution de CUJ de bout en bout (comme le défilement rapide de la grille de photos, la navigation dans les albums, le balayage de l'aperçu des photos et vidéos haute résolution en plein écran, ainsi que l'enregistrement et la sauvegarde des traces),
memory.higha été réduit tout en mesurant les compteursmemory.statde cgroup (workingset_refault_anon,workingset_refault_file,pgscanetpgsteal) etmemory.events(high). Comme illustré dans la figure 6, le budget optimal se situe juste au-dessus du point d'inflexion zéro défaut. Le seuil de zéro défaut est le seuil auquel le noyau récupère le cache de fichiers froids à usage unique (pgsteal > 0) sans déclencher de thrashing de l'ensemble de travail actif (workingset_refault == 0). - Validation de la mise à l'échelle des pixels d'affichage sur différentes résolutions : les tests de
:PhotoPickersur quatre résolutions d'affichage physiques et émulées, d'environ 1,2 Mpx (720 × 1 600) à environ 4,5 Mpx (2 076 × 2 152, écran intérieur déplié), ont confirmé que son ensemble de travail de bitmap Glide et ViewPager2 actif évolue de manière linéaire avec le nombre de pixels d'affichage. La référence sans affichage varie également selon l'appareil : pour un nombre de pixels presque identique, le point d'inflexion sans défaut était d'environ 140 Mo sur l'écran externe d'un appareil pliable de 16 Go, contre 110 à 125 Mo sur un téléphone de 6 Go. Une référence de 85 Mo plus 25 octets par pixel d'affichage (android:maxMb="85"etandroid:additionalBytesPerDisplayPixel="25") reste au-dessus du point d'inflexion mesuré sur chaque écran testé (~110 Mo sur HD+, ~145 Mo sur FHD+ et ~190 Mo sur les écrans dépliés). - Vérification de la récupération de mémoire ART en arrière-plan : dans cgroup v2, les pages d'images
boot.artet de framework partagées par Zygote restent chargées dans le cgroup de Zygote et sont traitées comme un espace immunisé lors de la récupération de mémoire ART CMC (Concurrent Mark-Compact). Les tests de contrainte ont confirmé que lorsqu'une application d'interface utilisateur système passe àbackground, le resserrement dememory.high(par exemple, à 30 Mo surTraceur) limite proprement le tas de mémoire privé et le cache de pages sans provoquer de thrashing de récupération de mémoire ni d'activité d'échange.
Figure 6. Trouver le point d'inflexion de zéro défaut lors d'un CUJ.
Résumé des budgets des applications système de référence AOSP
Le tableau suivant récapitule les budgets de référence calibrés dans l'AOSP pour les différents niveaux de RAM et états de processus :
| Package ou processus système | Champ d'application | ≤ 8 Go de RAM | 12 Go de RAM | ≥ 16 Go de RAM | Pilote de calibrage principal |
|---|---|---|---|---|---|
com.android.bluetooth |
UID (<application>) |
80 Mo | 95 Mo | 110 Mo | Couvre les pics de memory.peak de durée de vie P90 (~75 à 110 Mo) et > 2x de memory.current actif P99. |
com.android.nfc |
UID (<application>) |
50 Mo | 60 Mo | 60 Mo | Couvre l'émulation de carte hôte active P99 (jusqu'à environ 50 Mo) et P90 à P95 memory.peak tout en réduisant le cache de fichiers inactifs. |
com.android.providers.calendar |
UID (<application>) |
70 Mo par défaut (premier plan) 50 Mo perceptible et background |
L'ensemble de travail actif est invariant entre les niveaux de RAM. Le budget en arrière-plan conserve une marge de manœuvre anonyme pour les alarmes de rappel tout en réduisant le cache de pages SQLite lors des synchronisations. | ||
com.android.traceur |
UID (<application>) |
85 Mo par défaut (premier plan et perceptible) 30 Mo background |
Conserve une marge de 85 Mo pendant l'enregistrement et l'exportation de traces actives (TraceService), tout en limitant l'état d'inactivité et de diffusion à 30 Mo. |
||
com.android.providers.media.module (principal) |
PID (<process>) |
60 Mo | 75 Mo | 85 Mo | Fournit une marge d'environ 10 à 30 Mo par rapport au anon + swap P99 tout en récupérant le cache des fichiers SQLite et des vignettes inactifs. |
com.android.providers.media.module:PhotoPicker |
PID (<process>) |
85 Mo + 25 octets par pixel (~110 Mo en HD+, ~145 Mo en FHD+, ~190 Mo en ~4,5 MP plié) |
S'ajuste proportionnellement au nombre de pixels de l'écran (additionalBytesPerDisplayPixel="25") pour correspondre au point d'inflexion de l'ensemble de travail du bitmap Glide. |
||
com.android.networkstack |
UID (<application>) |
65 Mo | 70 Mo | 85 Mo | Mémoire principalement anonyme, dimensionnée pour la durée de vie P95 memory.peak afin de couvrir les pics de TLS, DNS et DHCP du portail captif. |
com.android.appsearch.apk |
UID (<application>) |
130 Mo | Couvre environ 100 Mo de mémoire verrouillée et non expulsable, plus un ensemble de travail d'hôte actif d'environ 5 Mo, avec une marge de croissance pour la région verrouillée. Zéro défaut de page mesuré à ≥ 125 Mo pendant la récupération du cache de pages froid (jusqu'à environ 330 Mo au 99e centile). | ||
com.android.phone (TeleService) |
PID (<process>) |
120 Mo | Limité par PID sur l'UID partagé 1001. Le jeu de travail suit la génération de l'appareil plutôt que la RAM. Un seul budget se situe donc environ 30 Mo au-dessus du anon + swap P99 le plus élevé par modèle. |
||
Comment les OEM peuvent recalibrer les budgets des applications système pour leurs propres appareils
Les OEM qui personnalisent les applications système (par exemple, en ajoutant des profils Bluetooth de fournisseurs, des extensions de téléphonie personnalisées ou des indexeurs multimédias OEM) ou qui fournissent des services système préinstallés supplémentaires peuvent recalibrer les budgets existants ou créer des fichiers *-budget.xml pour leur portefeuille de matériel.
Collecter les profils de mémoire cgroup v2 actifs
Pour chaque processus système cible, lisez ses nœuds de mémoire cgroup v2 sous /sys/fs/cgroup/uid_<uid>/pid_<pid>/ pour les charges de travail et les niveaux de RAM représentatifs :
memory.current: mémoire résidente actuelle facturée au cgroup (à l'exclusion des pages partagées par Zygote)memory.peak: pic dememory.currentdepuis le début du processusmemory.swap.current: utilisation actuelle de l'espace d'échange compressé dans la ZRAMmemory.stat: répartition deanon,file,pgscan,pgsteal,workingset_refault_anonetworkingset_refault_filememory.events: nombre cumulé de violations dehigh(événements de limitation et de récupérationmemory.high)
Lorsque vous agrégez les données de télémétrie d'appareils de test ou de terrain, séparez les états de processus actifs (TOP, FOREGROUND_SERVICE, liaisons actives et synchronisations actives) des états CACHED ou PERSISTENT inactifs afin que les instantanés inactifs ne faussent pas vos calculs de jeu de travail P95 et P99 à la baisse.
Séparer le tas incompressible du cache de fichiers récupérable
Comparez le 99e centile de anon + swap au 99e centile de memory.current :
- Services dominés par le cache de fichiers (
file≫anon, tels que les fournisseurs de bases de données, les indexeurs de recherche et les démons de journalisation) : définissezandroid:maxMbpour fournir une marge confortable (généralement de 1,5x à 4x) par rapport auanon + swapP99, plus une fenêtre de travail pour les pages de fichiers actives. Cette configuration permet au noyau d'expulser en continu les pages de fichiers non mappées inactives sans toucher au tas anonyme. Siunevictableoufile_mappedest volumineux (comme avec la mémoire verrouillée ou les bases de données mappées en mémoire), comptabilisez également cette empreinte comme un ensemble de travail. - Services dominés par un tas anonyme ou en rafale (
anonconstitue la majeure partie dememory.current, comme les piles de mise en réseau ou de connectivité) : dimensionnezandroid:maxMbpour couvrir les percentiles 90 à 95 dememory.peakafin que les pics temporaires ne déclenchent pas de permutation ZRAM synchrone sur les threads sensibles à la latence. - Processus d'interface utilisateur gourmands en affichage (tels que les sélecteurs, les galeries ou les assistants d'interface utilisateur système) : mesurez l'empreinte de référence hors interface utilisateur et l'empreinte bitmap incrémentielle par mégapixel sur vos écrans de résolution la plus faible et la plus élevée, et exprimez le budget à l'aide de
android:maxMbplusandroid:additionalBytesPerDisplayPixel.
Balayer memory.high sur les appareils physiques
Avant d'allouer un nouveau budget à la compilation, utilisez am memory-limiter manual (ou écrivez directement dans le nœud cgroup memory.high du processus sur une compilation userdebug) pour tester les limites candidates lors de l'exécution de vos tests CUJ automatisés :
# Override memory.high for a specific PID to 60 MB during a CUJ test
adb shell am memory-limiter manual <PID> 60MBSurveillez memory.stat et memory.events avant et après le CUJ :
- Budget trop faible :
workingset_refault_anonouworkingset_refault_fileaugmente fortement pendant le CUJ,memory.swap.currentaugmente continuellement pendant le travail actif au premier plan, ou la latence de la frame CUJ ou de Binder régresse. Augmentez le budget au-delà du point d'inflexion par défaut. - Le budget est trop élevé :
memory.events:highreste0même après des charges de travail d'indexation ou de streaming de fichiers importantes, etfiledansmemory.stataugmente de manière illimitée avec les pages froides. Réduisez le budget pour l'ensemble de travail actif. - Le budget est bien calibré : le
memory.currenten régime permanent reste inférieur àmemory.highen fonctionnement normal. Lors de pics d'activité importants liés aux fichiers ou au cache,pgstealrécupère les pages de fichiers froids, tandis queworkingset_refault_anonreste proche de zéro et que la latence CUJ reste inchangée.