In Android 17 e versioni successive, la piattaforma gestisce la memoria dei processi di sistema utilizzando due meccanismi cgroup v2 di Linux complementari che funzionano insieme al limitatore di memoria generale dell'applicazione:
- Soglie di protezione dei daemon di sistema (
SystemMemoryBudgetManagerconmemory.min): le prenotazioni cgroup v2memory.mincon ottimizzazione automatica (impostate al 90% del working set residente stabilito di ogni daemon e limitate a livello globale al 10% della RAM fisica totale) proteggono i daemon interattivi critici e i processi della shell (system_server,surfaceflinger, l'HAL graficocomposer,audioserver,cameraserver,netd,SystemUIe l'Avvio app Home) in caso di pressione della memoria, sostituendo il blocco statico dei filePinnerServicemlock. - Limiti di memoria delle app di sistema (
<memory-budget>conmemory.high): i limiti flessibili di cgroup v2memory.highcalibrati in base al workload (in genere da 30 MB a 190 MB) dichiarati nei file manifest per le app di sistema persistenti e in background (Bluetooth,Nfc,CalendarProvider,Traceur,MediaProvider,:PhotoPicker,NetworkStack,AppSearcheTeleService) recuperano in modo proattivo la cache delle pagine dei file cold e l'heap anonimo post-burst prima che si verifichi una pressione della memoria globale.
La tabella seguente mette a confronto i due meccanismi:
| Dimensione | Livelli minimi di protezione dei daemon di sistema | Limiti di memoria delle app di sistema |
|---|---|---|
| Servizio di piattaforma | SystemMemoryBudgetManager |
MemoryLimiter |
| Controllo cgroup v2 | memory.min hard protection floor |
Soglia massima di recupero soft di memory.high |
| Processi target | Daemon interattivi con latenza critica e UI della shell (system_server, surfaceflinger, HAL composer, audioserver, cameraserver, netd, SystemUI e l'app Home) |
App di sistema persistenti e headless, provider di contenuti e servizi in background (come Bluetooth, Nfc, MediaProvider, NetworkStack, AppSearch e TeleService) |
| Modello di dimensionamento | Ottimizzazione automatica in fase di esecuzione (90% del working set stabilito, con un limite globale del 10% della RAM totale) senza tabelle di ottimizzazione per dispositivo | Attributi XML del file manifest <memory-budget> dichiarativo calibrati in base al livello di RAM, allo stato del processo e al numero di pixel del display |
| Vantaggio principale | Impedisce i rallentamenti della UI interattiva e i blocchi di riattivazione in caso di pressione della memoria durante la sostituzione degli elenchi di file PinnerService mlock statici |
Rimuove continuamente la cache dei file monouso inattivi e l'heap post-burst per lasciare più RAM fisica alle app in primo piano e memorizzate nella cache |
Per ulteriori informazioni sulla sintassi <memory-budget>, sugli attributi XML, sulle API di runtime e sul conteggio della memoria del sistema operativo, consulta Imposta i budget di memoria delle app e Informazioni e risoluzione dei problemi relativi alla memoria Android.
Proteggi i daemon interattivi critici con SystemMemoryBudgetManager
SystemMemoryBudgetManager mantiene i processi che si trovano direttamente nel percorso interattivo dell'utente residenti nella RAM fisica durante la pressione della memoria. Anziché bloccare un elenco statico di file APK, ODEX e VDEX in memoria con mlock (come fa PinnerService per config_defaultPinnerServiceFiles e config_pinnerHomePinBytes), SystemMemoryBudgetManager assegna valori minimi dinamici di cgroup v2 memory.min ai seguenti processi:
- Sezione del sistema di base (
/sys/fs/cgroup/system):system_server,surfaceflinger, l'HALcomposergrafico,audioserver,cameraserverenetd, oltre a eventuali daemon di sistema o fornitore nativi specifici del fattore di forma elencati inconfig_systemMemoryBudgetAdditionalSystemProcesses(ad esempio,android.hardware.perceptionhosteauracamerahalsui dispositivi XR). - Sezione dell'app interattiva (
/sys/fs/cgroup/apps): il processo principale del pacchetto SystemUI configurato (config_systemUi) e il processo principale del titolare del ruolo Home dell'utente corrente.
La protezione del titolare del ruolo Home insieme a config_systemUi garantisce la copertura
su tutti i fattori di forma. Sui fattori di forma in cui config_systemUi è un pacchetto di compatibilità leggero (ad esempio Wear OS, in cui il quadrante, le notifiche e i riquadri vengono eseguiti all'interno dell'app Home), la shell rivolta agli utenti riceve comunque una protezione completa del working set. Se l'intent Home viene risolto nel pacchetto config_systemUi, il processo viene protetto una volta. Solo il processo principale
di ogni pacchetto (processName == packageName) riceve un livello di protezione, quindi
i sottoprocessi ausiliari (come :screenshot) non consumano il budget di protezione.
Policy di ottimizzazione automatica e delega dei cgroup a tre livelli
Poiché il set di lavoro di un daemon interattivo varia notevolmente in base ai fattori di forma e alle capacità della RAM (ad esempio, SystemUI utilizza circa 45 MB su uno smartwatch, 60 MB su uno smartphone da 6 GB e 180 MB su uno smartphone da 16 GB), le tabelle di ricerca statiche per dispositivo sono fragili. Invece,
SystemMemoryBudgetManager ricava i floor direttamente dal footprint residente di ogni processo utilizzando tre costanti:
| Parametro della policy | Valore predefinito | Comportamento |
|---|---|---|
| Prezzo minimo per processo | 90% di memory.current liquidato meno la cache dei file non mappati |
Protegge heap anonimo, slab e codice mappato caldo, lasciando recuperabile la coda del 10% più fredda, in modo che le pagine veramente fredde possano comunque essere rimosse. La cache dei file non mappati (max(0, file - shmem - file_mapped) da memory.stat) viene sottratta prima di applicare il rapporto del 90%, in modo che la cache di scansione del pacchetto di avvio o le letture degli asset una tantum non aumentino i valori minimi. |
| Limite di RAM globale | 10% della RAM totale (config_systemMemoryBudgetLowKb) |
Corrisponde alla regola di protezione config_pinnerMaxPinnedMemoryPercentage PinnerService. Se la somma dei prezzi minimi del 90% supera il 10% di MemTotal, ogni prezzo minimo viene ridotto proporzionalmente dello stesso fattore. |
| Cadenza di aggiornamento | 1, 4, 16 e 64 secondi dopo l'avvio, poi ogni 30 minuti | Una serie di assestamento geometrico converge sul working set post-avvio di ogni daemon senza impostare come hardcoded i ritardi di riscaldamento per dispositivo, mentre un aggiornamento dello stato stabile di 30 minuti monitora la crescita e la riduzione graduali del working set nel corso del ciclo di vita del dispositivo. |
Nel controller di memoria cgroup v2 di Linux (mm/memcontrol.c:effective_protection), la protezione memory.min effettiva di un cgroup foglia è limitata dal valore memory.min configurato nei cgroup padre e predecessore. L'impostazione di memory.min solo su una directory pid_<pid> foglia mentre uid_<uid>/memory.min è 0 non offre alcuna protezione effettiva.
Al contrario, l'impostazione di un valore memory.min fisso solo sulla sezione /sys/fs/cgroup/system superiore distribuisce la protezione non rivendicata tra tutti gli UID secondari e i processi in modo proporzionale (memory_recursiveprot), consentendo ai processi diagnostici o secondari che condividono uid_1000 (android.uid.system) di sottrarre la protezione da surfaceflinger e audioserver.
Per fornire un isolamento esatto per daemon, SystemMemoryBudgetManager scrive
la protezione su tutti e tre i livelli della gerarchia cgroup v2 a ogni aggiornamento,
come mostrato nella figura 1:
- Nodo del processo foglia (
pid_<pid>/memory.min): impostato sul limite minimo del working set del 90% (scalato) del processo. - Nodo UID (
uid_<uid>/memory.min): impostato sulla somma esatta dei suoi floor foglia gestitipid_*, in modo che i processi non gestiti che condividono l'UID (come altri daemonAID_SYSTEMo pacchettisharedUserId="android.uid.system") abbianomemory.min = 0e non possano richiedere la protezione. - Sezione di primo livello (
/sys/fs/cgroup/system/memory.mine/sys/fs/cgroup/apps/memory.min): impostata sulla somma esatta delle sezioni secondarieuid_*. Tutti i valorimemory.lowobsoleti sui nodi gestiti vengono cancellati e impostati su0, e tutte le directoryuid_*gestite in un aggiornamento precedente (ad esempio un Avvio app Home precedente dopo che l'utente cambia Avvio app) vengono reimpostate su0.
Figura 1. Delega del livello di protezione a tre livelli tra le sezioni cgroup.
Perché viene utilizzato memory.min anziché memory.low sui kernel MGLRU
In LRU multigenerazionale (MGLRU, il motore di recupero delle pagine predefinito sui kernel Android 6.6 e versioni successive), memory.low e memory.min si comportano in modo diverso in mm/vmscan.c:shrink_one:
memory.low(rinvio a passaggio singolo): un cgroup il cui utilizzo è inferiore amemory.lowviene ruotato alla fine della LRU memcg (MEMCG_LRU_TAIL) e ignorato per un singolo passaggio di recupero. Al passaggio successivo, in caso di pressione della memoria prolungata, il kernel recupera completamente il cgroup e registra un eventoMEMCG_LOW. Negli stress test sui dispositivi fisici con un consumo di memoria anonimo che cresce fino all'85% della RAM fisica,memory.lowha consentito aSystemUIsu uno smartwatch di scendere da circa 45 MB a circa 5-10 MB e aaudioserversu un pieghevole da 16 GB di scendere da 15 MB a 2 MB rispetto a un minimo di circa 14 MB.memory.min(hard protection floor): un cgroup il cui utilizzo è inferiore amemory.minviene ignorato dal recupero globale. Come mostrato nella figura 2, con lo stesso workload con pressione della RAM dell'85%,memory.minelimina solo la coda del 10% più fredda di ogni daemon e mantieneSystemUI,surfaceflingereaudioserverai loro livelli minimi del 90% con zero refault anonimi e nessun aumento delle terminazioni dei processilmkd.
memory.min fornisce una prenotazione fisica rigida che corrisponde alla protezione del pinning PinnerService mlock che sostituisce, con il vantaggio che l'LRU del kernel (anziché un elenco di file statico) sceglie quali pagine di codice hot, heap e grafica rimangono residenti.
Figura 2. Conservazione del working set del daemon in caso di aumento della pressione della memoria.
Preriscaldamento all'avvio e recupero della cache dei file non mappati dopo l'avvio
La sostituzione della mlock permanente PinnerService di
config_defaultPinnerServiceFiles richiede il coordinamento di due comportamenti
all'avvio:
- Preriscaldamento dell'avvio in background una tantum (
PinnerService): saltarePinnerServicemlocksenza riscaldare la cache della pagina sposta circa 95 MB di letture del disco APK, ODEX e VDEX diSystemUInel percorso di avvio sincrono all'avvio diSystemUI(OnBootPhase_550), aggiungendo da 300 a 450 ms al completamento dell'avvio. La chiamata dimadvise(MADV_WILLNEED)non è sufficiente perchéforce_page_cache_ranel kernel blocca una singola chiamataMADV_WILLNEEDa una finestra di lettura anticipata (in genere da 128 KB a 2 MB). Invece, quandoSystemMemoryBudgetManager.shouldSkipDefaultSystemPinningè attivo,PinnerServicemappa ogni file inconfig_defaultPinnerServiceFilessu un thread in background duranteonStart, compila in modo sincrono il file nella cache della pagina conOs.mlocke chiama immediatamenteOs.munlock,munmapeclose. In questo modo, i tempi di avvio rapido vengono preservati, lasciando zero byte bloccati in modo permanente, così le pagine di avvio a freddo possono essere rimosse in un secondo momento. - Esclusione e recupero della cache di scansione dei pacchetti di avvio di
system_server:durante l'avvio,PackageManagerServicescansiona gli APK installati, lasciando da 1,5 a 2,5 GB di cache delle pagine dei file non mappati a freddo addebitati al cgroupsystem_server. Se inclusa nel calcolo dimemory.mindisystem_server, la cache di avvio a freddo satura il limite globale del 10% della RAM e riduce proporzionalmente i floor di protezione disurfaceflinger,SystemUIeaudioserver. Per evitare la carenza di capacità,SystemMemoryBudgetManagerleggememory.statpersystem_server, calcola la cache dei file non mappati comemax(0, file - shmem - file_mapped)e sottrae la cache non mappata damemory.currentprima di calcolare il limite minimo del 90%, come mostrato nella figura 3. Inoltre, 10 secondi dopoPHASE_BOOT_COMPLETED, il gestore esegue una scrittura una tantum di<unmapped_file> swappiness=0nel nodomemory.reclaimdisystem_serversui kernel Linux 6.2 e versioni successive per rimuovere in modo proattivo la cache di scansione dei pacchetti inattivi senza scambiare l'heap anonimo o rimuovere le pagine DEX e di codice mappate. Sui kernel meno recenti (comeandroid14-6.1) che rifiutanoswappiness=0conEINVAL, il gestore salta la scritturamemory.reclaimanziché eseguire il fallback a una scrittura di recupero non qualificata che scambierebbe l'heap anonimo durante l'avvio.
Figura 3. Convergenza della soglia di protezione e recupero della cache dopo l'avvio.
Requisiti del kernel e overlay di configurazione dispositivo
SystemMemoryBudgetManager si basa su mem_cgroup_calculate_protection nel kernel Linux per propagare memory.min tra i cgroup nidificati. I kernel comuni di Android android15-6.6 e android16-6.12 includono il supporto completo della protezione nidificata MGLRU integrato. I dispositivi con android14-6.1 con MGLRU
attivato richiedono tre backport upstream in mm/vmscan.c e mm/memcontrol.c
in modo che le sezioni intermedie vuote e i cgroup UID propaghino la protezione ai cgroup dei processi foglia:
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)
Gli OEM possono personalizzare o disattivare SystemMemoryBudgetManager utilizzando i seguenti
overlay delle risorse del framework:
config_systemMemoryBudgetLowKb(numero intero, kB):0(predefinito) limita la somma di tutti i floormemory.minal 10% della RAM fisica totale. Un valore positivo imposta un limite esplicito in kB, mentre un valore negativo disattivaSystemMemoryBudgetManagersul dispositivo (mantenendo il blocco staticomlockdiPinnerServiceprecedente).config_systemMemoryBudgetHighKb(numero intero, kB):0(valore predefinito) lascia/sys/fs/cgroup/system/memory.highamax. Un valore positivo imposta un limite massimo per la sezione superiorememory.high(deve essere maggiore o uguale al limite minimo).config_systemMemoryBudgetAdditionalSystemProcesses(string-array): elenca nomi di processi daemon di sistema o del fornitore nativi aggiuntivi con latenza critica in/sys/fs/cgroup/system(confrontati con/proc/<pid>/commo il nome di base diargv[0]in/proc/<pid>/cmdlineper gli UID di sistema e del fornitore in10000) da proteggere con soglie minime del 90% dimemory.mininsieme ai sei daemon di sistema predefiniti. Utilizza questo overlay anziché impostarememory.minininit.rcperchéSystemMemoryBudgetManagerricalcolauid_<uid>/memory.mincome somma dei processi foglia gestiti e cancella i nodiuid_*non gestiti a ogni aggiornamento. Ad esempio, un dispositivo XR può proteggere i daemon HAL di tracciamento spaziale e della fotocamera con il seguente overlay:<string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses"> <item>android.hardware.perceptionhost</item> <item>auracamerahal</item> </string-array>config_systemUi(stringa): nome del pacchetto dell'app SystemUI della piattaforma, risolto conMATCH_SYSTEM_ONLY.
Per ispezionare i floor SystemMemoryBudgetManager e i nodi cgroup attivi su un dispositivo, esegui:
# 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
Associa app di sistema persistenti e in background con budget di memoria
Sebbene SystemMemoryBudgetManager protegga i daemon interattivi dal recupero utilizzando i livelli memory.min, molte altre app di sistema e servizi della piattaforma con privilegi vengono eseguiti in background per lunghi periodi come processi PERSISTENT, servizi in primo piano associati o provider di contenuti sottoposti a query frequenti.
Senza limiti di memoria espliciti, questi processi non vengono limitati finché non raggiungono i limiti di Memory Limiter di più gigabyte (ad esempio da 4 GB a 10 GB per le app visibili e da 2 GB a 5 GB per le app in background) o finché la pressione della memoria globale non attiva l'attività di kswapd e del killer di memoria insufficiente (lmkd) a livello di sistema.
Se non vengono limitati, i processi di sistema in background spesso accumulano grandi quantità di memoria cold che non fa parte del loro set di lavoro attivo:
- Cache delle pagine dei file illimitata:i servizi che indicizzano i contenuti multimediali, eseguono ricerche nei database o sincronizzano le tabelle di calendario e provider (come
AppSearch,MediaProvidereCalendarProvider) mappano o trasmettono in streaming file di grandi dimensioni sul disco. Senza un limite massimo di cgroupmemory.high, le pagine pulite con backup su file provenienti da letture una tantum rimangono addebitate al cgroup del processo a tempo indeterminato, consumando centinaia di megabyte di RAM fisica. - Conservazione dell'heap anonimo post-burst:i servizi che subiscono picchi di attività temporanei, come i burst di rilevamento audio e BLE di
Bluetooth, la convalida TLS del captive portal diNetworkStacko gli helper dell'interfaccia utente che passano in background, possono conservare le pagine dell'heap anonimo inattive molto tempo dopo il completamento del burst.
L'applicazione di dichiarazioni <memory-budget> calibrate (limiti flessibili memory.high, in genere da 30 MB a 190 MB) alle app di sistema offre due vantaggi principali:
- Recupero proattivo e localizzato delle pagine:quando un processo di sistema supera il budget
memory.highconfigurato, il kernel Linux indirizza il recupero delle pagine specificamente a quel cgroup, rimuovendo prima le pagine cold supportate da file e comprimendo le pagine anonime inattive in ZRAM senza attendere la pressione della memoria globale del dispositivo. - Più RAM disponibile per le app in primo piano e il pool di app memorizzate nella cache: la limitazione delle cache dei file cold nei servizi di sistema sempre attivi recupera da decine a centinaia di megabyte di RAM fisica sul dispositivo, riducendo i problemi di UI, la pressione di avvio della fotocamera e mantenendo attive più app utente nello stato memorizzato nella cache.
Configura i budget di memoria delle app di sistema
Le app di sistema utilizzano la stessa sintassi del manifest <memory-budget>, la stessa cascata dello stato del processo, la stessa scalabilità della densità di visualizzazione e la stessa regola di risoluzione dell'ultima clausola corrispondente delle app generali. Per l'elenco completo degli attributi, consulta il riferimento agli attributi XML.
Gli OEM possono dichiarare gli elementi <memory-budget> direttamente all'interno del file AndroidManifest.xml di un'app di sistema o unirli da un file XML separato utilizzando la proprietà additional_manifests in Android.bp. Per convenzione, AOSP archivia i budget delle app di sistema di riferimento in file *-budget.xml autonomi (in frameworks/base/core/memory_budgets/ per i pacchetti della piattaforma principale o insieme a singoli moduli come packages/modules/NetworkStack/memory_budgets/, packages/modules/AppSearch/apk/ e packages/services/Telephony/) e li allega con additional_manifests:
android_app {
name: "TeleService",
manifest: "AndroidManifest.xml",
additional_manifests: ["Telephony-budget.xml"],
// ...
}
Ambito a livello di pacchetto (UID) rispetto all'ambito a livello di processo (PID)
La dichiarazione di un budget di base a livello di pacchetto (<application>) o di un budget multiprocesso (<processes><process>) determina se il kernel applica memory.high a livello di cgroup UID (/sys/fs/cgroup/uid_<uid>/memory.high) o a livello di cgroup per PID (/sys/fs/cgroup/uid_<uid>/pid_<pid>/memory.high), come mostrato nella figura 4:
- Budget a livello di pacchetto (
<application>):da utilizzare quando l'APK esegue un singolo processo con il proprio UID dedicato (ad esempioBluetooth,Nfc,NetworkStack,CalendarProvider,TraceuroAppSearch). - Budget a livello di processo (
<processes><process>): utilizza questa opzione quando l'APK condivideandroid:sharedUserId(ad esempioandroid.uid.phonecon UID1001oandroid.uid.systemcon UID1000) con altri pacchetti di sistema, in modo che altri processi che condividono l'UID non vengano limitati insieme o quando un singolo APK esegue più processi con ruoli di memoria distinti (ad esempio, il daemonMediaProviderin background e il sottoprocesso UI:PhotoPicker).
Figura 4. Limiti di memoria a livello di UID e di PID.
L'esempio seguente mostra MediaProvider-budget.xml, che limita i budget con livelli di RAM al processo MediaProvider predefinito (<process>) e un budget con scalabilità del display al processo UI :PhotoPicker. Ogni attributo android:maxDeviceRamMb corrisponde ai dispositivi la cui RAM fisica totale (MemTotal) è pari o inferiore alla soglia di MB specificata (o si applica a qualsiasi capacità della RAM se omessa). Le clausole sono ordinate dal valore predefinito non vincolato fino alla soglia maxDeviceRamMb più piccola, in modo che la regola di risoluzione dell'ultima clausola corrispondente selezioni il livello applicabile più restrittivo:
<?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>
Come sono stati calibrati i budget di riferimento AOSP sui dispositivi Google Pixel
I budget di memoria delle app di sistema di riferimento in AOSP sono stati calibrati su tutti i dispositivi Google Pixel con una RAM fisica compresa tra 6 GB e 16 GB (inclusi smartphone, pieghevoli e tablet da 6 GB, 8 GB, 12 GB e 16 GB) e risoluzioni del display comprese tra ~1, 2 MP (720 x 1600) e ~4, 5 MP (2076 x 2152).
La calibrazione ha combinato due origini dati complementari: la telemetria dei campi cgroup v2 a livello di parco risorse e le scansioni memory.high dei dispositivi fisici controllati nei Critical User Journey (CUJ).
Analisi della telemetria di cgroup v2 su tutto il parco risorse
Sono stati raccolti snapshot periodici di cgroup v2 nel parco dispositivi e suddivisi
per livello di RAM fisica (≤8 GB, 12 GB e ≥16 GB) e bucket di stato
del processo (foreground, perceptible, background e cached). L'analisi statistica è stata guidata da sei
principi:
- Scomposizione di
memory.currentin working set e cache dei file recuperabile: poichémemory.highregola l'addebito del cgroup residente (memory.current), ogni snapshot è stato scomposto utilizzandomemory.statin heap anonimo (anon), pagine anonime scambiate (swap) e cache delle pagine con backup su file (file), come mostrato nella figura 5. La domanda anonima incomprimibile totale (anon + swapa P99) stabilisce il limite minimo del working set per il budget, in modo che il processo non rischi l'esaurimento della ZRAM, mentre un ampio divario tramemory.currentP99 eanon + swapP99 indica una cache delle pagine dei file chememory.highpuò recuperare, a condizione che la cache non sia mappata e sia inattiva. - Tieni conto della memoria che
memory.highnon può recuperare: non tutta la memoria dei file e condivisa è cache cold. Le pagine bloccate (unevictableinmemory.stat, ad esempio la memoria condivisa bloccata conmlocko la RAM guest bloccata di una macchina virtuale i cui processi host vengono addebitati al cgroup dell'app) impostano un limite minimo per il budget e le pagine mappate (file_mapped) fanno spesso parte del set di lavoro attivo. Controllaunevictable,shmemefile_mappedprima di considerare la memoria dei file come recuperabile. Ad esempio, circa 100 MB di memoriaAppSearchsono bloccati e non possono essere rimossi, quindi il budget copre questo limite minimo più il working set dell'host attivo, con spazio per la crescita della regione bloccata. - Filtra la diluizione dello stato di inattività:i provider di contenuti (
CalendarProvider) e gli strumenti per sviluppatori on demand (Traceur) trascorrono dal 70% al 99,9% degli snapshot sul campo in stato di inattività con un consumo di circa 1-3 MB. Il calcolo dei percentili non elaborati in tutti gli snapshot diluisce notevolmente le stime P95 e P99. Invece, i percentili e gli intervalli di affidabilità sono stati calcolati su snapshot attivi non diluiti (filtrando gli snapshotCACHEDe gli snapshot di riferimento inattivi). - Utilizza intervalli di affidabilità raggruppati per dispositivo:poiché un singolo dispositivo contribuisce con molti snapshot periodici nel tempo, trattare gli snapshot come campioni indipendenti sottovaluta l'incertezza della coda. Tutte le stime del working set P95 e P99 sono state valutate utilizzando intervalli di affidabilità conservativi del 95% raggruppati per dispositivo (utilizzando il numero di dispositivi distinti
N_devcome dimensione effettiva del campione). - Considera i burst temporanei con
memory.peak: il campionamento periodico può non rilevare burst di breve durata con allocazione elevata (come la convalida TLS del captive portalNetworkStacke i probe DNS simultanei o l'inizializzazione diBluetoothA2DP e BLE). Per i servizi con burst, i budget sono stati dimensionati per coprire il 90° e il 95° percentile dei valori massimi del kernelmemory.peaknel corso del ciclo di vita nei livelli di RAM. - Verifica che un livello di RAM rifletta la RAM, non la generazione del dispositivo:i dispositivi più recenti tendono ad avere più RAM e più funzionalità, quindi un working set che cresce con il livello di RAM potrebbe in realtà crescere con le funzionalità. Prima di aggiungere un livello
maxDeviceRamMb, suddividi i dati per modello di dispositivo. Ad esempio,com.android.phoneraggiunge lo stesso P99anon + swap(~85-90 MB) su smartphone da 12 GB e 16 GB della stessa generazione, mentre un pieghevole da 12 GB più vecchio utilizza 40 MB. Il suo budget utilizza quindi un unico valore senza livello di RAM, in modo che un dispositivo con meno RAM che include le nuove funzionalità di telefonia non abbia un budget insufficiente.
Figura 5. Working set rispetto alla cache dei file recuperabili.
CUJ del dispositivo fisico e sweep di scalabilità del display
Per convalidare i budget derivati dalla telemetria e calibrare i budget proporzionali al display (come :PhotoPicker) e i budget in background specifici per lo stato (come Traceur), sono state eseguite scansioni memory.high in tempo reale su dispositivi fisici con RAM da 6 GB a 16 GB:
- Individuazione del punto di flesso con zero errori di ricaricamento: durante l'esecuzione di CUJ end-to-end (come lo scorrimento rapido della griglia di foto, la navigazione negli album, lo scorrimento dell'anteprima di foto e video ad alta risoluzione a schermo intero e la registrazione e il salvataggio delle tracce),
memory.highè stato ridotto mentre venivano misurati i contatori cgroupmemory.stat(workingset_refault_anon,workingset_refault_file,pgscanepgsteal) ememory.events(high). Come illustrato nella figura 6, il budget ottimale si trova appena sopra il punto di flesso con zero errori di ricaricamento. Il punto di flesso zero-refault è la soglia in cui il kernel recupera la cache dei file cold monouso (pgsteal > 0) senza attivare il thrashing del working set attivo (workingset_refault == 0). - Convalida della scalabilità dei pixel del display tra le risoluzioni: i test di
:PhotoPickersu quattro risoluzioni del display fisiche ed emulate da ~1,2 MP (720 x 1600) a ~4,5 MP (2076 x 2152 con display interno aperto) hanno confermato che il set di lavoro bitmap attivo di Glide e ViewPager2 scala in modo lineare con il numero di pixel del display. Anche la base di riferimento non display varia in base al dispositivo: con un numero di pixel quasi identico, il punto di flesso di zero refault era di circa 140 MB sul display esterno di un pieghevole da 16 GB rispetto a 110-125 MB su uno smartphone da 6 GB. Una base di riferimento di 85 MB più 25 byte per pixel del display (android:maxMb="85"eandroid:additionalBytesPerDisplayPixel="25") rimane al di sopra del punto di flesso misurato su ogni display testato (~110 MB su HD+, ~145 MB su FHD+ e ~190 MB su display aperti). - Verifica della garbage collection di ART negli stati in background: in cgroup v2, le pagine di immagini del framework e
boot.artcondivise da Zygote rimangono addebitate al cgroup di Zygote e vengono trattate come uno spazio immune durante la garbage collection di ART Concurrent Mark-Compact (CMC). Gli stress test hanno confermato che quando un'app della UI di sistema passa abackground, l'applicazione dimemory.highpiù restrittivi (ad esempio, a 30 MB suTraceur) limita in modo pulito l'heap privato e la cache delle pagine senza causare thrashing del GC o attività di swap.
Figura 6. Individuazione del punto di flesso con zero errori di riferimento durante un percorso utente critico.
Riepilogo dei budget delle app di sistema di riferimento AOSP
La tabella seguente riassume i budget di riferimento calibrati in AOSP per i livelli di RAM e gli stati dei processi:
| Pacchetto o processo di sistema | Ambito | ≤8 GB di RAM | 12 GB di RAM | ≥16 GB di RAM | Driver di calibrazione principale |
|---|---|---|---|---|---|
com.android.bluetooth |
UID (<application>) |
80 MB | 95 MB | 110 MB | Copre i burst memory.peak di durata P90 (~75-110 MB) e >2x memory.current attivi P99. |
com.android.nfc |
UID (<application>) |
50 MB | 60 MB | 60 MB | Copre l'Host Card Emulation attiva P99 (fino a ~50 MB) e da P90 a P95 memory.peak durante il trimming della cache dei file cold. |
com.android.providers.calendar |
UID (<application>) |
70 MB predefinito (foreground) 50 MB perceptible e background |
Il set di lavoro attivo è invariante tra i livelli di RAM. Il budget in background mantiene un margine anonimo per le sveglie dei promemoria, riducendo al contempo la cache della pagina SQLite durante le sincronizzazioni. | ||
com.android.traceur |
UID (<application>) |
85 MB predefiniti (foreground e percepibile) 30 MB background |
Mantiene l'headroom completo di 85 MB durante la registrazione e l'esportazione delle tracce attive (TraceService), limitando al contempo lo stato di inattività e di trasmissione a 30 MB. |
||
com.android.providers.media.module (principale) |
PID (<process>) |
60 MB | 75 MB | 85 MB | Fornisce circa 10-30 MB di margine rispetto al P99 anon + swap, recuperando al contempo la cache dei file di miniature e SQLite inattivi. |
com.android.providers.media.module:PhotoPicker |
PID (<process>) |
85 MB + 25 byte per pixel (~110 MB su HD+, ~145 MB su FHD+, ~190 MB su pieghevole da ~4,5 MP) |
Scala proporzionalmente al numero di pixel del display (additionalBytesPerDisplayPixel="25") per corrispondere al punto di flesso del working set della bitmap Glide. |
||
com.android.networkstack |
UID (<application>) |
65 MB | 70 MB | 85 MB | Memoria prevalentemente anonima, dimensionata in base alla durata P95 memory.peak per coprire i burst TLS, DNS e DHCP del captive portal. |
com.android.appsearch.apk |
UID (<application>) |
130 MB | Copre circa 100 MB di memoria bloccata e non espellibile, oltre a un working set di host hot di circa 5 MB, con spazio per l'espansione della regione bloccata. Zero errori di pagina misurati a ≥125 MB mentre viene recuperata la cache di pagina cold (fino a ~330 MB a P99). | ||
com.android.phone (TeleService) |
PID (<process>) |
120 MB | Ambito per PID su UID condiviso 1001. Il working set monitora la generazione del dispositivo anziché la RAM, quindi un singolo budget è di circa 30 MB superiore al P99 anon + swap più alto per modello. |
||
Come gli OEM possono ricalibrare i budget delle app di sistema per i propri dispositivi
Gli OEM che personalizzano le app di sistema (ad esempio, aggiungendo profili Bluetooth del fornitore,
estensioni di telefonia personalizzate o indicizzatori multimediali OEM) o che forniscono servizi di sistema
preinstallati aggiuntivi possono ricalibrare i budget esistenti o creare nuovi
file *-budget.xml per il loro portafoglio hardware.
Raccogli i profili di memoria cgroup v2 attivi
Per ogni processo del sistema di destinazione, leggi i nodi di memoria cgroup v2 in /sys/fs/cgroup/uid_<uid>/pid_<pid>/ tra i workload e i livelli di RAM rappresentativi:
memory.current: memoria residente corrente addebitata al cgroup (escluse le pagine condivise da Zygote)memory.peak: picco massimo dimemory.currentdall'avvio del processomemory.swap.current: utilizzo attuale dello swap compresso in ZRAMmemory.stat: suddivisione dianon,file,pgscan,pgsteal,workingset_refault_anoneworkingset_refault_filememory.events: conteggio cumulativo delle violazioni dihigh(eventi di limitazione e recupero dimemory.high)
Quando aggreghi la telemetria dai dispositivi dogfood o sul campo, separa gli stati dei processi attivi (TOP, FOREGROUND_SERVICE, associazioni attive e sincronizzazioni attive) dagli stati di inattività CACHED o PERSISTENT, in modo che gli snapshot di inattività non alterino i calcoli del working set P95 e P99 verso il basso.
Separa l'heap non comprimibile dalla cache dei file recuperabili
Confronta il 99° percentile di anon + swap con il 99° percentile di
memory.current:
- Servizi dominati dalla cache dei file (
file≫anon, come i provider di database, gli indicizzatori dell'indice della Ricerca e i daemon di log): impostaandroid:maxMbin modo da fornire un margine confortevole (in genere da 1, 5x a 4x) rispetto aanon + swapP99 più una finestra di lavoro per le pagine dei file attivi. Questa configurazione consente al kernel di rimuovere continuamente le pagine di file non mappate inattive senza toccare l'heap anonimo. Seunevictableofile_mappedè di grandi dimensioni (come nel caso della memoria bloccata o dei database mappati in memoria), conteggia anche questo footprint come working set. - Servizi dominati da heap anonimo o con burst (
anoncostituisce la maggior parte dimemory.current, ad esempio stack di networking o connettività): dimensionaandroid:maxMbper coprire da P90 a P95 dimemory.peakin modo che i burst temporanei non attivino lo swap-out sincrono di ZRAM su thread sensibili alla latenza. - Processi UI ad alta intensità di visualizzazione (come selettori, gallerie o helper UI di sistema): misura l'impatto di base non UI e l'impatto incrementale della bitmap per megapixel sui display con risoluzione più bassa e più alta ed esprimi il budget utilizzando
android:maxMbpiùandroid:additionalBytesPerDisplayPixel.
Sweep memory.high sui dispositivi fisici
Prima di assegnare un nuovo budget alla build, utilizza am memory-limiter manual (o scrivi direttamente nel nodo cgroup memory.high del processo in una build userdebug) per testare i limiti candidati durante l'esecuzione dei test CUJ automatici:
# Override memory.high for a specific PID to 60 MB during a CUJ test
adb shell am memory-limiter manual <PID> 60MBMonitora memory.stat e memory.events prima e dopo il CUJ:
- Il budget è troppo basso:
workingset_refault_anonoworkingset_refault_fileaumenta notevolmente durante il CUJ,memory.swap.currentcresce continuamente durante il lavoro attivo in primo piano o la latenza del frame CUJ o di Binder regredisce. Aumenta il budget oltre il punto di flesso del refault. - Il budget è troppo alto:
memory.events:highrimane0anche dopo carichi di lavoro intensi di streaming o indicizzazione dei file efileinmemory.statcresce senza limiti con le pagine cold. Limita il budget al set di lavoro attivo. - Il budget è ben calibrato: l'
memory.currentin stato stabile rimane al di sotto dimemory.highdurante il normale funzionamento. Durante i burst di file o cache pesanti,pgstealrecupera le pagine dei file cold, mentreworkingset_refault_anonrimane vicino allo zero e la latenza CUJ rimane invariata.