In Android 17 und höher wird der Arbeitsspeicher für Systemprozesse über zwei sich ergänzende Linux-cgroup-v2-Mechanismen verwaltet, die zusammen mit dem allgemeinen Memory Limiter für Anwendungen funktionieren:
- System-Daemon-Schutzuntergrenzen (
SystemMemoryBudgetManagermitmemory.min): Selbstoptimierende cgroup v2-memory.min-Reservierungen (festgelegt auf 90 % des festgelegten residenten Arbeitssatzes jedes Daemons und global auf 10% des gesamten physischen RAM begrenzt) schützen kritische interaktive Daemons und Shell-Prozesse (system_server,surfaceflinger, die Grafik-composer-HAL,audioserver,cameraserver,netd,SystemUIund den Home Launcher) bei Speicherdruck und ersetzen das statische Anpinnen vonPinnerService-mlock-Dateien. - Speicherobergrenzen für System-Apps (
<memory-budget>mitmemory.high): Die auf die Arbeitslast abgestimmten weichen Limits von cgroup v2memory.high(normalerweise 30 MB bis 190 MB), die in Manifestdateien für persistente und im Hintergrund ausgeführte System-Apps (Bluetooth,Nfc,CalendarProvider,Traceur,MediaProvider,:PhotoPicker,NetworkStack,AppSearchundTeleService) deklariert sind, geben proaktiv den Cache für kalte Dateiseiten und den anonymen Heap nach einem Burst frei, bevor globaler Speicherdruck auftritt.
In der folgenden Tabelle werden die beiden Mechanismen verglichen:
| Dimension | Schutzebenen für System-Daemons | Speicherobergrenzen für System-Apps |
|---|---|---|
| Plattformdienst | SystemMemoryBudgetManager |
MemoryLimiter |
| Cgroup v2-Steuerung | memory.min harter Schutzboden |
memory.high Obergrenze für die teilweise Rückforderung |
| Zielprozesse | Latenzkritische interaktive Daemons und Shell-UI (system_server, surfaceflinger, composer HAL, audioserver, cameraserver, netd, SystemUI und die Home App) |
Persistente und monitorlose System-Apps, Contentanbieter und Hintergrunddienste (z. B. Bluetooth, Nfc, MediaProvider, NetworkStack, AppSearch und TeleService) |
| Modell zur Größenanpassung | Selbstoptimierung zur Laufzeit (90% des festgelegten Arbeitssatzes, global auf 10% des gesamten RAM begrenzt) ohne gerätespezifische Optimierungstabellen | Deklarative XML-Attribute im <memory-budget>-Manifest, die nach RAM-Stufe, Prozessstatus und Displaypixelanzahl kalibriert sind |
| Hauptvorteil | Verhindert Ruckeln der interaktiven Benutzeroberfläche und Blockierungen beim Aufwachen bei Speichermangel, während statische PinnerService-mlock-Dateilisten ersetzt werden |
Entfernt kontinuierlich kalten Einmal-Dateicache und Post-Burst-Heap, um mehr physischen RAM für Vordergrund- und im Cache gespeicherte Apps freizugeben |
Weitere Informationen zur <memory-budget>-Syntax, zu XML-Attributen, Laufzeit-APIs und zur Arbeitsspeicherabrechnung des Betriebssystems finden Sie unter Arbeitsspeicherbudgets für Apps festlegen und Android-Arbeitsspeicher verstehen und Fehler beheben.
Kritische interaktive Daemons mit SystemMemoryBudgetManager schützen
SystemMemoryBudgetManager sorgt dafür, dass die Prozesse, die sich direkt im interaktiven Pfad des Nutzers befinden, bei Speicherdruck im physischen RAM verbleiben. Anstatt eine statische Liste von APK-, ODEX- und VDEX-Dateien mit mlock im Arbeitsspeicher zu fixieren (wie es PinnerService für config_defaultPinnerServiceFiles und config_pinnerHomePinBytes tut), weist SystemMemoryBudgetManager den folgenden Prozessen dynamische cgroup v2-memory.min-Untergrenzen zu:
- Core-System-Slice (
/sys/fs/cgroup/system):system_server,surfaceflinger, die Grafik-composer-HAL,audioserver,cameraserverundnetdsowie alle formfaktorspezifischen nativen System- oder Anbieter-Daemons, die inconfig_systemMemoryBudgetAdditionalSystemProcessesaufgeführt sind (z. B.android.hardware.perceptionhostundauracamerahalauf XR-Geräten). - Interaktiver App-Slice (
/sys/fs/cgroup/apps): Der Hauptprozess des konfigurierten SystemUI-Pakets (config_systemUi) und der Hauptprozess des aktuellen Nutzers, der die Rolle „Home“ innehat.
Wenn du neben config_systemUi auch den Inhaber der Rolle „Zuhause“ schützt, sind alle Formfaktoren abgedeckt. Bei Formfaktoren, bei denen config_systemUi ein einfaches Kompatibilitätspaket ist (z. B. Wear OS, wo das Zifferblatt, die Benachrichtigungen und die Kacheln in der Home App ausgeführt werden), erhält die für Nutzer sichtbare Shell weiterhin den vollen Schutz des Working Sets. Wenn der Home-Intent zum Paket config_systemUi aufgelöst wird, wird der Prozess einmal geschützt. Nur der Hauptprozess jedes Pakets (processName == packageName) erhält eine Schutzuntergrenze, sodass Hilfssubprozesse (z. B. :screenshot) kein Schutzbudget verbrauchen.
Richtlinie zur Selbstoptimierung und dreistufige cgroup-Delegation
Da der Arbeitssatz eines interaktiven Daemons je nach Formfaktor und RAM-Kapazität stark variiert (z. B. verwendet SystemUI etwa 45 MB auf einer Smartwatch, 60 MB auf einem Smartphone mit 6 GB und 180 MB auf einem Smartphone mit 16 GB), sind statische gerätespezifische Suchtabellen unzuverlässig. Stattdessen leitet SystemMemoryBudgetManager die Untergrenzen direkt aus dem eigenen Resident-Footprint jedes Prozesses mithilfe von drei Konstanten ab:
| Richtlinienparameter | Standardwert | Verhalten |
|---|---|---|
| Mindestpreis pro Prozess | 90% des abgerechneten memory.current abzüglich des nicht zugeordneten Datei-Caches |
Schützt anonymen Heap, Slab und Hot-Mapped-Code, während die kältesten 10% des Tails wiederherstellbar bleiben, sodass wirklich kalte Seiten weiterhin entfernt werden können. Der nicht zugeordnete Dateicache (max(0, file - shmem - file_mapped) von memory.stat) wird abgezogen, bevor das Verhältnis von 90% angewendet wird, damit der Cache für den Boot-Paket-Scan oder einmalige Asset-Lesevorgänge die Mindestwerte nicht künstlich erhöhen. |
| Globale RAM-Obergrenze | 10% des gesamten RAM (config_systemMemoryBudgetLowKb) |
Entspricht der Schutzmaßnahme PinnerService config_pinnerMaxPinnedMemoryPercentage. Wenn die Summe der 90-%-Mindestpreise 10% von MemTotal überschreitet, wird jeder Mindestpreis proportional um denselben Faktor herabgesetzt. |
| Aktualisierungsrhythmus | 1, 4, 16 und 64 Sekunden nach dem Start, dann alle 30 Minuten | Eine geometrische Stabilisierungsreihe konvergiert auf dem Arbeitssatz jedes Daemons nach dem Start, ohne dass die Aufwärmverzögerungen pro Gerät hartcodiert werden müssen. Eine 30-minütige Aktualisierung im stabilen Zustand verfolgt das allmähliche Wachstum und die Verkleinerung des Arbeitssatzes über die Lebensdauer des Geräts. |
Im Linux-cgroup v2-Speichercontroller (mm/memcontrol.c:effective_protection) ist der effektive memory.min-Schutz einer Blatt-cgroup durch den memory.min-Wert begrenzt, der für die übergeordneten und übergeordneten cgroups konfiguriert ist. Wenn Sie memory.min nur für ein Blattverzeichnis pid_<pid> festlegen, während uid_<uid>/memory.min auf 0 gesetzt ist, führt dies zu einem effektiven Schutz von null.
Wenn Sie dagegen einen pauschalen memory.min nur für das oberste /sys/fs/cgroup/system-Segment festlegen, wird der nicht beanspruchte Schutz proportional auf alle untergeordneten UIDs und Prozesse verteilt (memory_recursiveprot). Dadurch können Diagnose- oder sekundäre Prozesse, die uid_1000 (android.uid.system) gemeinsam nutzen, Schutz von surfaceflinger und audioserver abziehen.
Um eine genaue Isolation pro Daemon zu ermöglichen, schreibt SystemMemoryBudgetManager bei jeder Aktualisierung Schutzmaßnahmen auf allen drei Ebenen der cgroup v2-Hierarchie, wie in Abbildung 1 dargestellt:
- Blattprozessknoten (
pid_<pid>/memory.min): Auf die (skalierte) 90-%-Untergrenze des Arbeitssatzes des Prozesses festgelegt. - UID-Knoten (
uid_<uid>/memory.min): Wird auf die genaue Summe der verwaltetenpid_*-Blatt-Floors festgelegt, sodass nicht verwaltete Prozesse, die diese UID gemeinsam nutzen (z. B. andereAID_SYSTEM-Daemons odersharedUserId="android.uid.system"-Pakete),memory.min = 0haben und keinen Schutz beanspruchen können. - Slice auf oberster Ebene (
/sys/fs/cgroup/system/memory.minund/sys/fs/cgroup/apps/memory.min): Auf die genaue Summe der untergeordnetenuid_*-Elemente festgelegt. Alle veraltetenmemory.low-Werte auf verwalteten Knoten werden auf0gelöscht und alleuid_*-Verzeichnisse, die bei einer vorherigen Aktualisierung verwaltet wurden (z. B. ein vorheriger Home-Launcher, nachdem der Nutzer den Launcher gewechselt hat), werden auf0zurückgesetzt.
Abbildung 1. Dreistufige Delegierung des Schutzlevels über cgroup-Slices hinweg.
Warum „memory.min“ anstelle von „memory.low“ auf MGLRU-Kerneln verwendet wird
Unter Multi-Generational LRU (MGLRU, die Standard-Engine zur Seitenrückgewinnung auf Android 6.6 und neueren Kernels) verhalten sich memory.low und memory.min in mm/vmscan.c:shrink_one unterschiedlich:
memory.low(Zurückstellung mit einem Durchlauf): Eine cgroup, deren Nutzung untermemory.lowliegt, wird an das Ende der memcg-LRU (MEMCG_LRU_TAIL) verschoben und für einen einzelnen Freigabedurchlauf übersprungen. Beim nächsten Durchlauf unter anhaltendem Arbeitsspeicherdruck wird die cgroup vollständig freigegeben und einMEMCG_LOW-Ereignis protokolliert. In Stresstests auf physischen Geräten mit einem anonymen Speicherfresser, der auf 85% des physischen RAM anwuchs, ermöglichtememory.low, dassSystemUIauf einer Smartwatch von ca. 45 MB auf ca. 5 bis 10 MB undaudioserverauf einem faltbaren Gerät mit 16 GB von 15 MB auf 2 MB bei einem Mindestwert von ca. 14 MB sanken.memory.min(harte Schutzuntergrenze): Eine cgroup, deren Nutzung untermemory.minliegt, wird von der globalen Freigabe übersprungen. Wie in Abbildung 2 dargestellt, werden bei derselben Arbeitslast mit 85% RAM-Auslastungmemory.minnur die kältesten 10% der einzelnen Daemons entfernt.SystemUI,surfaceflingerundaudioserverwerden bei 90% gehalten, ohne dass anonyme Refaults auftreten und ohne dass die Anzahl derlmkd-Prozessbeendigungen steigt.
memory.min bietet eine feste physische Reservierung, die dem Schutz des PinnerService-mlock-Pinnings entspricht, das es ersetzt. Der Vorteil ist, dass die Kernel-LRU (und nicht eine statische Dateiliste) auswählt, welche Hot-Code-, Heap- und Grafikseiten resident bleiben.
Abbildung 2: Aufbewahrung des Daemon-Arbeitssatzes bei steigender Speicherauslastung.
Vorwärmen beim Booten und Freigabe des nicht zugeordneten Datei-Caches nach dem Booten
Wenn Sie die permanente mlock von config_defaultPinnerServiceFiles durch PinnerService ersetzen, müssen Sie zwei Verhaltensweisen beim Booten koordinieren:
- Einmaliges Vorwärmen des Hintergrundstarts (
PinnerService): WennPinnerServicemlockübersprungen wird, ohne den Seitencache vorzuwärmen, werden beim Start vonSystemUI(OnBootPhase_550) etwa 95 MB anSystemUIAPK-, ODEX- und VDEX-Laufwerkslesevorgängen auf den synchronen Start pfad verschoben, was die Startzeit um 300 bis 450 ms verlängert. Der Aufruf vonmadvise(MADV_WILLNEED)reicht nicht aus, daforce_page_cache_raim Kernel einen einzelnenMADV_WILLNEED-Aufruf auf ein Readahead-Fenster (normalerweise 128 KB bis 2 MB) beschränkt. Stattdessen ordnetPinnerService, wennSystemMemoryBudgetManager.shouldSkipDefaultSystemPinningaktiv ist, jede Datei inconfig_defaultPinnerServiceFileswährendonStarteinem Hintergrundthread zu, füllt die Datei synchron mitOs.mlockin den Seitencache und ruft sofortOs.munlock,munmapundcloseauf. Dadurch wird die Schnellstartzeit beibehalten, während keine Bytes dauerhaft gesperrt werden, sodass Seiten für den Kaltstart später entfernt werden können. - Ausschluss und Rückforderung des
system_server-Boot-Paket-Scan-Cache: Während des Bootvorgangs scanntPackageManagerServiceinstallierte APKs und hinterlässt 1,5 bis 2,5 GB nicht zugeordneten Dateiseiten-Cache, der dersystem_server-Cgroup zugerechnet wird. Wenn der Kaltstart-Cache in diememory.min-Berechnung vonsystem_servereinbezogen wird, füllt er die globale RAM-Obergrenze von 10% aus und skaliert die Schutzuntergrenzen vonsurfaceflinger,SystemUIundaudioserverproportional herunter. Um eine Obergrenzen-Unterversorgung zu verhindern, liestSystemMemoryBudgetManagermemory.statfürsystem_server, berechnet den nicht zugeordneten Dateicache alsmax(0, file - shmem - file_mapped)und subtrahiert diesen nicht zugeordneten Cache vonmemory.current, bevor die 90-%-Untergrenze berechnet wird, wie in Abbildung 3 dargestellt. Außerdem führt der Manager 10 Sekunden nachPHASE_BOOT_COMPLETEDeinen einmaligen Schreibvorgang von<unmapped_file> swappiness=0in denmemory.reclaim-Knoten vonsystem_serverauf Linux 6.2 und neueren Kerneln aus, um den kalten Paketscan-Cache proaktiv zu entfernen, ohne anonymen Heap auszulagern oder zugeordnete DEX- und Codeseiten zu entfernen. Bei älteren Kernels (z. B.android14-6.1), dieswappiness=0mitEINVALablehnen, überspringt der Manager den Schreibvorgangmemory.reclaim, anstatt auf einen unqualifizierten Schreibvorgang für die Rückgewinnung zurückzugreifen, der den anonymen Heap während des Bootvorgangs auslagern würde.
Abbildung 3: Konvergenz der Schutzuntergrenze und Cache-Rückgewinnung nach dem Booten.
Kernel-Anforderungen und Overlays für die Gerätekonfiguration
SystemMemoryBudgetManager basiert auf mem_cgroup_calculate_protection im Linux-Kernel, um memory.min über verschachtelte cgroups zu verteilen. Die Android Common Kernels android15-6.6 und android16-6.12 bieten standardmäßig vollständige Unterstützung für den verschachtelten MGLRU-Schutz. Geräte mit android14-6.1 und aktiviertem MGLRU erfordern drei Upstream-Backports in mm/vmscan.c und mm/memcontrol.c, damit leere Zwischen-Slice- und UID-cgroups den Schutz auf Blattprozess-cgroups übertragen:
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)
OEMs können SystemMemoryBudgetManager mithilfe der folgenden Framework-Ressourcen-Overlays anpassen oder deaktivieren:
config_systemMemoryBudgetLowKb(Ganzzahl, KB):0(Standard) begrenzt die Summe allermemory.min-Untergrenzen auf 10% des gesamten physischen RAM. Ein positiver Wert legt eine explizite Obergrenze in KB fest und ein negativer Wert deaktiviertSystemMemoryBudgetManagerauf dem Gerät (wobei das alte statischemlock-Pinning vonPinnerServicebeibehalten wird).config_systemMemoryBudgetHighKb(Ganzzahl, KB):0(Standard) belässt/sys/fs/cgroup/system/memory.highbeimax. Ein positiver Wert legt eine Obergrenze für das Top-Slice-memory.highfest (muss größer oder gleich der Untergrenze sein).config_systemMemoryBudgetAdditionalSystemProcesses(String-Array): Listet zusätzliche latenzkritische native System- oder Anbieter-Daemon-Prozessnamen in/sys/fs/cgroup/systemauf (abgeglichen mit/proc/<pid>/commoder dem Basisnamen vonargv[0]in/proc/<pid>/cmdlinefür System- und Anbieter-UIDs unter10000), die neben den sechs Standard-System-Daemons mit 90%memory.min-Untergrenzen geschützt werden sollen. Verwenden Sie dieses Overlay, anstattmemory.minininit.rcfestzulegen, daSystemMemoryBudgetManageruid_<uid>/memory.minals Summe der verwalteten Blattprozesse neu berechnet und nicht verwalteteuid_*-Knoten bei jeder Aktualisierung löscht. Ein XR-Gerät kann beispielsweise seine Spatial-Tracking- und Kamera-HAL-Daemons mit dem folgenden Overlay schützen:<string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses"> <item>android.hardware.perceptionhost</item> <item>auracamerahal</item> </string-array>config_systemUi(String): Paketname der SystemUI-App der Plattform, aufgelöst mitMATCH_SYSTEM_ONLY.
Führen Sie den folgenden Befehl aus, um aktive SystemMemoryBudgetManager-Untergrenzen und cgroup-Knoten auf einem Gerät zu prüfen:
# 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
Persistente und im Hintergrund ausgeführte System-Apps mit Arbeitsspeicherbudgets begrenzen
SystemMemoryBudgetManager schützt interaktive Daemons mithilfe von memory.min-Untergrenzen vor der Rückforderung. Viele andere System-Apps und privilegierte Plattformdienste werden jedoch über längere Zeiträume im Hintergrund als PERSISTENT-Prozesse, gebundene Dienste im Vordergrund oder häufig abgefragte Contentanbieter ausgeführt.
Ohne explizite Arbeitsspeicher-Obergrenzen werden diese Prozesse erst dann eingeschränkt, wenn sie die Memory Limiter-Limits von mehreren Gigabyte erreichen (z. B. 4 GB bis 10 GB für sichtbare Apps und 2 GB bis 5 GB für Hintergrund-Apps) oder wenn die globale Arbeitsspeicherauslastung systemweite kswapd- und Low-Memory-Killer-Aktivitäten (lmkd) auslöst.
Wenn sie nicht eingeschränkt werden, sammeln sich in Hintergrundsystemprozessen oft große Mengen an Cold Memory an, der nicht Teil ihres aktiven Arbeitssatzes ist:
- Unbegrenzter Dateiseitencache: Dienste, die Medien indexieren, Datenbanken durchsuchen oder Kalender- und Anbietertabellen synchronisieren (z. B.
AppSearch,MediaProviderundCalendarProvider), ordnen große Dateien auf dem Laufwerk zu oder streamen sie. Ohne einememory.high-Obergrenze für die cgroup werden bereinigte dateibasierte Seiten aus einmaligen Lesevorgängen auf unbestimmte Zeit der Prozess-cgroup in Rechnung gestellt, was Hunderte von Megabyte physischen RAM verbraucht. - Anonyme Heap-Aufbewahrung nach Aktivitätsspitzen:Dienste mit vorübergehenden Aktivitätsspitzen, z. B.
Bluetooth-Audio- und BLE-Erkennungsspitzen,NetworkStack-TLS-Validierung für Captive Portale oder UI-Hilfsprogramme, die in den Hintergrund wechseln, können kalte anonyme Heap-Seiten noch lange nach Abschluss der Aktivitätsspitze aufbewahren.
Die Anwendung kalibrierter <memory-budget>-Deklarationen (memory.high-Soft-Limits, in der Regel 30 MB bis 190 MB) auf System-Apps bietet zwei Hauptvorteile:
- Proaktive, lokalisierte Seitenrückgewinnung: Wenn ein Systemprozess das konfigurierte
memory.high-Budget überschreitet, weist der Linux-Kernel die Seitenrückgewinnung speziell dieser cgroup zu. Dabei werden zuerst kalte dateibasierte Seiten entfernt und inaktive anonyme Seiten in ZRAM komprimiert, ohne auf globalen Gerätespeicherdruck zu warten. - Mehr verfügbarer RAM für Apps im Vordergrund und den Cache-App-Pool:Durch die Begrenzung von Cold-File-Caches in Always-On-Systemdiensten werden auf dem Gerät Dutzende bis Hunderte Megabyte physischer RAM freigegeben. Dadurch werden UI-Ruckler reduziert, die Kamera wird schneller gestartet und mehr Nutzer-Apps bleiben im Cache-Status aktiv.
Speicherbudgets für System-Apps konfigurieren
System-Apps verwenden dieselbe <memory-budget>-Manifestsyntax, Prozessstatus-Kaskadierung, Skalierung der Anzeigedichte und Auflösungsregel für die letzte übereinstimmende Klausel wie allgemeine Apps. Eine vollständige Liste der Attribute finden Sie in der XML-Attributreferenz.
OEMs können <memory-budget>-Elemente direkt in der AndroidManifest.xml einer System-App deklarieren oder sie mithilfe des Attributs additional_manifests in Android.bp aus einer separaten XML-Datei zusammenführen. Gemäß der Konvention speichert das AOSP die Budgets für Referenzsystem-Apps in eigenständigen *-budget.xml-Dateien (entweder in frameworks/base/core/memory_budgets/ für Kernplattformpakete oder zusammen mit einzelnen Modulen wie packages/modules/NetworkStack/memory_budgets/, packages/modules/AppSearch/apk/ und packages/services/Telephony/) und hängt sie mit additional_manifests an:
android_app {
name: "TeleService",
manifest: "AndroidManifest.xml",
additional_manifests: ["Telephony-budget.xml"],
// ...
}
Paketebene (UID) im Vergleich zur Prozessebene (PID)
Ob du ein Baseline-Budget auf Paketebene (<application>) oder ein Budget für mehrere Prozesse (<processes><process>) deklarierst, bestimmt, ob der Kernel memory.high auf der UID-cgroup-Ebene (/sys/fs/cgroup/uid_<uid>/memory.high) oder auf der PID-cgroup-Ebene (/sys/fs/cgroup/uid_<uid>/pid_<pid>/memory.high) erzwingt, wie in Abbildung 4 dargestellt:
- Budget auf Paketebene (
<application>):Verwende diese Option, wenn das APK einen einzelnen Prozess unter seiner eigenen dedizierten UID ausführt (z. B.Bluetooth,Nfc,NetworkStack,CalendarProvider,TraceuroderAppSearch). - Budget auf Prozessebene (
<processes><process>):Verwende diese Option, wenn das APKandroid:sharedUserId(z. B.android.uid.phonemit UID1001oderandroid.uid.systemmit UID1000) mit anderen Systempaketen teilt, damit andere Prozesse, die diese UID teilen, nicht zusammen gedrosselt werden, oder wenn ein einzelnes APK mehrere Prozesse mit unterschiedlichen Speicherrollen ausführt (z. B. den Hintergrund-DaemonMediaProviderund den UI-Unterprozess:PhotoPicker).
Abbildung 4: Speicherobergrenzen auf UID-Ebene im Vergleich zu PID-Ebene.
Im folgenden Beispiel wird MediaProvider-budget.xml gezeigt, wodurch RAM-gestaffelte Budgets auf den Standardprozess MediaProvider (<process>) und ein display-scaled budget auf den UI-Prozess :PhotoPicker beschränkt werden. Jedes android:maxDeviceRamMb-Attribut entspricht Geräten, deren physischer RAM (MemTotal) insgesamt dem angegebenen MB-Grenzwert entspricht oder darunter liegt. Wenn kein Grenzwert angegeben ist, gilt das Attribut für alle RAM-Kapazitäten. Die Klauseln sind von der uneingeschränkten Standardeinstellung bis zum kleinsten maxDeviceRamMb-Schwellenwert sortiert, sodass die Auflösungsregel für die letzte übereinstimmende Klausel die engste anwendbare Stufe auswählt:
<?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>
Wie AOSP-Referenzbudgets auf Google Pixel-Geräten kalibriert wurden
Die Referenzspeicherbudgets für System-Apps im AOSP wurden auf Google Pixel-Geräten mit 6 GB bis 16 GB physischem RAM (einschließlich Smartphones, faltbaren Geräten und Tablets mit 6 GB, 8 GB, 12 GB und 16 GB) und Displayauflösungen von ca. 1, 2 MP (720 × 1.600) bis ca. 4, 5 MP (2.076 × 2.152) kalibriert.
Für die Kalibrierung wurden zwei sich ergänzende Datenquellen kombiniert: flottenweite cgroup v2-Feldtelemetrie und kontrollierte memory.high-Sweeps auf physischen Geräten für wichtige Nutzerverhalten (Critical User Journey, CUJ).
Flottenweite cgroup v2-Telemetrieanalyse
Es wurden regelmäßig cgroup v2-Snapshots auf allen Geräten erfasst und nach physischer RAM-Stufe (≤ 8 GB, 12 GB und ≥ 16 GB) und Prozessstatus-Bucket (foreground, perceptible, background und cached) aufgeteilt. Die statistische Analyse wurde anhand von sechs Prinzipien durchgeführt:
memory.currentin Arbeitssatz und wiederherstellbaren Dateicache zerlegen: Damemory.highdie residente cgroup-Gebühr (memory.current) regelt, wurde jeder Snapshot mitmemory.statin anonymen Heap (anon), ausgelagerte anonyme Seiten (swap) und dateigestützten Seitencache (file) zerlegt, wie in Abbildung 5 dargestellt. Der gesamte inkomprimierbare anonyme Bedarf (anon + swapbei P99) legt die Mindestgröße des Arbeitssatzes für das Budget fest, damit der Prozess nicht Gefahr läuft, dass der ZRAM erschöpft wird. Eine große Lücke zwischenmemory.currentP99 undanon + swapP99 weist auf einen Dateiseitencache hin, denmemory.highzurückfordern kann, sofern der Cache nicht zugeordnet und kalt ist.- Speicher berücksichtigen, den
memory.highnicht freigeben kann: Nicht alle Dateien und freigegebenen Speicher sind Cold Cache. Gesperrte Seiten (unevictableinmemory.stat, z. B. mitmlockgesperrter gemeinsam genutzter Arbeitsspeicher oder der gesperrte Gast-RAM einer virtuellen Maschine, deren Hostprozesse der cgroup der App zugerechnet werden) legen eine feste Untergrenze für das Budget fest. Zugeordnete Seiten (file_mapped) sind oft Teil des aktiven Arbeitssatzes. Prüfen Sieunevictable,shmemundfile_mapped, bevor Sie Dateispeicher als freigebbar behandeln. Beispiel: Etwa 100 MB desAppSearch-Speichers sind gesperrt und können nicht entfernt werden. Das Budget deckt also diese Untergrenze sowie den aktiven Host-Arbeitssatz ab, wobei Spielraum für das Wachstum der gesperrten Region vorhanden ist. - Verwässerung im inaktiven Zustand herausfiltern:Contentanbieter (
CalendarProvider) und On-Demand-Entwicklertools (Traceur) verbringen 70% bis 99,9% der Feld-Snapshots im inaktiven Zustand mit etwa 1 MB bis 3 MB. Die Berechnung von Rohperzentilen über alle Snapshots hinweg verwässert die P95- und P99-Schätzungen erheblich. Stattdessen wurden Perzentile und Konfidenzintervalle über unverdünnte aktive Snapshots berechnet (wobeiCACHED-Snapshots und inaktive Baseline-Snapshots herausgefiltert wurden). - Gerätebezogene Konfidenzintervalle verwenden:Da ein einzelnes Gerät im Laufe der Zeit viele regelmäßige Snapshots liefert, wird die Unsicherheit bei der Behandlung von Snapshots als unabhängige Stichproben unterschätzt. Alle Schätzungen der Arbeitssätze für das 95. und 99. Perzentil wurden mit konservativen, gerätebasierten Konfidenzintervallen von 95 % bewertet. Dabei wurde die Anzahl der einzelnen Geräte (
N_dev) als effektive Stichprobengröße verwendet. - Berücksichtigen Sie vorübergehende Spitzen mit
memory.peak: Bei regelmäßigen Stichproben können kurzlebige Spitzen mit hoher Zuweisung übersehen werden (z. B. TLS-Validierung des Captive Portals vonNetworkStackund gleichzeitige DNS-Prüfungen oder Initialisierung vonBluetoothA2DP und BLE). Bei Diensten mit Lastspitzen wurden die Budgets so dimensioniert, dass sie das 90. bis 95. Perzentil der Höchstwerte für die Kernel-memory.peak-Lebensdauer über alle RAM-Stufen hinweg abdecken. - Prüfen, ob eine RAM-Stufe den RAM und nicht die Gerätegeneration widerspiegelt:Neuere Geräte haben in der Regel mehr RAM und mehr Funktionen. Ein Working Set, das mit der RAM-Stufe wächst, kann also tatsächlich mit den Funktionen wachsen. Bevor Sie eine
maxDeviceRamMb-Stufe hinzufügen, sollten Sie die Daten nach Gerätemodell aufteilen. Beispiel:com.android.phoneerreicht auf Smartphones mit 12 GB und 16 GB derselben Generation denselben P99-anon + swap(~85 bis 90 MB), während ein älteres faltbares Gerät mit 12 GB 40 MB verwendet. Das Budget verwendet daher einen einzelnen Wert ohne RAM-Stufe, damit ein Gerät mit weniger RAM, das die neueren Telefoniefunktionen bietet, nicht unterbudgetiert ist.
Abbildung 5: Arbeitssatz im Vergleich zu wiederherstellbarem Datei-Cache.
CUJ für physische Geräte und Sweeps für die Displayskalierung
Um die aus der Telemetrie abgeleiteten Budgets zu validieren und die displayproportionalen Budgets (z. B. :PhotoPicker) und die zustandsspezifischen Hintergrundbudgets (z. B. Traceur) zu kalibrieren, wurden Live-memory.high-Sweeps auf physischen Geräten mit 6 GB bis 16 GB RAM ausgeführt:
- Null-Refault-Knie finden:Während der Ausführung von End-to-End-CUJs (z. B. schnelles Scrollen im Fotoraster, Durchsuchen von Alben, Wischen durch hochauflösende Foto- und Videovorschauen im Vollbildmodus sowie Aufzeichnen und Speichern von Traces) wurde
memory.highschrittweise verringert, während die cgroup-Zählermemory.stat(workingset_refault_anon,workingset_refault_file,pgscanundpgsteal) undmemory.events(high) gemessen wurden. Wie in Abbildung 6 dargestellt, liegt das optimale Budget knapp über dem Null-Refault-Knie. Der Zero-Refault-Knick ist der Schwellenwert, bei dem der Kernel den kalten Einmal-Dateicache (pgsteal > 0) zurückfordert, ohne dass es zu einem aktiven Working-Set-Thrashing (workingset_refault == 0) kommt. - Validierung der Skalierung von Displaypixeln bei verschiedenen Auflösungen: Tests von
:PhotoPickermit vier physischen und emulierten Displayauflösungen von ca. 1,2 MP (720 × 1.600) bis ca. 4,5 MP (2.076 × 2.152, aufgeklapptes Innendisplay) haben bestätigt, dass die aktive Bitmap-Arbeitsgruppe von Glide und ViewPager2 linear mit der Anzahl der Displaypixel skaliert. Die Baseline ohne Display variiert ebenfalls je nach Gerät: Bei fast gleicher Pixelanzahl lag der Null-Refault-Knick bei einem faltbaren Gerät mit 16 GB RAM und einem äußeren Display bei etwa 140 MB, während er bei einem Smartphone mit 6 GB RAM bei 110 bis 125 MB lag. Eine Baseline von 85 MB plus 25 Byte pro Displaypixel (android:maxMb="85"undandroid:additionalBytesPerDisplayPixel="25") bleibt auf jedem getesteten Display über dem gemessenen Knick (~110 MB auf HD+, ~145 MB auf FHD+ und ~190 MB auf aufgeklappten Displays). - ART-Garbage-Collection im Hintergrund prüfen:In cgroup v2 werden von Zygote freigegebene
boot.art- und Framework-Image-Seiten weiterhin der cgroup von Zygote zugerechnet und während der ART-Garbage-Collection mit gleichzeitiger Markierung und Komprimierung (Concurrent Mark-Compact, CMC) als immuner Bereich behandelt. Stresstests haben bestätigt, dass beim Übergang einer System-UI-App zubackgrounddurch die Verschärfung vonmemory.high(z. B. auf 30 MB beiTraceur) der private Heap und der Seitencache sauber begrenzt werden, ohne dass es zu GC-Thrashing oder Swap-Aktivitäten kommt.
Abbildung 6. Den Punkt finden, an dem die Anzahl der Fehler während einer kritischen User Journey auf null sinkt.
Zusammenfassung der Budgets für AOSP-Referenzsystem-Apps
In der folgenden Tabelle sind die kalibrierten Referenzbudgets im AOSP für die verschiedenen RAM-Stufen und Prozessstatus zusammengefasst:
| Systempaket oder -prozess | Umfang | ≤ 8 GB RAM | 12 GB RAM | ≥16 GB RAM | Primärer Kalibrierungstreiber |
|---|---|---|---|---|---|
com.android.bluetooth |
UID (<application>) |
80 MB | 95 MB | 110 MB | Deckt P90-Lebensdauer-memory.peak-Bursts (~75 bis 110 MB) und >2x P99-aktive memory.current ab. |
com.android.nfc |
UID (<application>) |
50 MB | 60 MB | 60 MB | Deckt die aktive Host Card Emulation P99 (bis zu ~50 MB) und P90 bis P95 memory.peak ab, während der Cold-File-Cache gekürzt wird. |
com.android.providers.calendar |
UID (<application>) |
70 MB Standard (Vordergrund) 50 MB perceptible und background |
Der aktive Arbeitssatz ist über alle RAM-Klassen hinweg unveränderlich. Das Hintergrundbudget hält anonymen Spielraum für Erinnerungsalarme bereit, während der SQLite-Seiten-Cache während der Synchronisierung gekürzt wird. | ||
com.android.traceur |
UID (<application>) |
85 MB Standard (Vordergrund und wahrnehmbar) 30 MB background |
Behält während der aktiven Trace-Aufzeichnung und des Exports (TraceService) die vollen 85 MB Headroom bei, während der Ruhe- und Broadcast-Status auf 30 MB begrenzt wird. |
||
com.android.providers.media.module (Hauptschlaf) |
PID (<process>) |
60 MB | 75 MB | 85 MB | Bietet etwa 10 bis 30 MB mehr Spielraum als P99 anon + swap, während kalte SQLite- und Thumbnail-Dateicaches freigegeben werden. |
com.android.providers.media.module:PhotoPicker |
PID (<process>) |
85 MB + 25 Byte pro Pixel (~110 MB bei HD+, ~145 MB bei FHD+, ~190 MB bei ~4,5 MP Fold) |
Wird proportional zur Anzahl der Displaypixel (additionalBytesPerDisplayPixel="25") skaliert, um dem Knickpunkt des Glide-Bitmap-Arbeitssatzes zu entsprechen. |
||
com.android.networkstack |
UID (<application>) |
65 MB | 70 MB | 85 MB | Hauptsächlich anonymer Arbeitsspeicher, der auf die P95-Lebensdauer von memory.peak dimensioniert ist, um TLS-, DNS- und DHCP-Bursts von Captive Portals abzudecken. |
com.android.appsearch.apk |
UID (<application>) |
130 MB | Deckt ca. 100 MB gesperrten, nicht entfernbaren Arbeitsspeicher plus einen ca. 5 MB großen Hot-Host-Arbeitssatz ab, mit Spielraum für das Wachstum des gesperrten Bereichs. Null Refaults bei ≥ 125 MB, während der kalte Seitencache (bis zu ~330 MB bei P99) freigegeben wird. | ||
com.android.phone (TeleService) |
PID (<process>) |
120 MB | Auf PID-Ebene auf der gemeinsam genutzten UID 1001 beschränkt. Der Arbeitssatz verfolgt die Gerätegeneration und nicht den RAM. Daher liegt ein einzelnes Budget etwa 30 MB über dem höchsten P99-anon + swap pro Modell. |
||
Wie OEMs die Budgets für System-Apps für ihre eigenen Geräte neu kalibrieren können
OEMs, die System-Apps anpassen (z. B. durch Hinzufügen von Bluetooth-Profilen von Drittanbietern, benutzerdefinierten Telefonieerweiterungen oder OEM-Medienindexern) oder zusätzliche vorinstallierte Systemdienste bereitstellen, können vorhandene Budgets neu kalibrieren oder neue *-budget.xml-Dateien für ihr Hardwareportfolio erstellen.
Aktive cgroup v2-Speicherprofile erfassen
Lesen Sie für jeden Zielsystemprozess die cgroup v2-Speicherknoten unter /sys/fs/cgroup/uid_<uid>/pid_<pid>/ für repräsentative Arbeitslasten und RAM-Stufen:
memory.current: Aktueller residenter Speicher, der der cgroup in Rechnung gestellt wird (ohne von Zygote freigegebene Seiten)memory.peak: Höchstwert fürmemory.currentseit Prozessstartmemory.swap.current: Aktuelle komprimierte Auslagerungsnutzung in ZRAMmemory.stat: Aufschlüsselung vonanon,file,pgscan,pgsteal,workingset_refault_anonundworkingset_refault_filememory.events: Kumulative Anzahl vonhigh-Verstößen (memory.high-Drosselungs- und Rückforderungsereignisse)
Wenn du Telemetriedaten von Dogfood- oder Feldgeräten aggregierst, trenne aktive Prozesszustände (TOP, FOREGROUND_SERVICE, aktive Bindungen und aktive Synchronisierungen) von inaktiven CACHED- oder inaktiven PERSISTENT-Zuständen, damit inaktive Snapshots deine P95- und P99-Berechnungen für den Arbeitssatz nicht nach unten verzerren.
Nicht komprimierbaren Heap vom wiederherstellbaren Datei-Cache trennen
Vergleichen Sie das 99. Perzentil von anon + swap mit dem 99. Perzentil von memory.current:
- Dienste, die vom Dateicache dominiert werden (
file>>anon, z. B. Datenbankanbieter, Suchindexer und Log-Daemons): Legen Sieandroid:maxMbso fest, dass ein ausreichender Spielraum (normalerweise das 1,5- bis 4-Fache) über P99anon + swapplus ein Arbeitsfenster für häufig verwendete Dateiseiten vorhanden ist. Mit dieser Konfiguration kann der Kernel kontinuierlich kalte, nicht zugeordnete Dateiseiten entfernen, ohne den anonymen Heap zu berühren. Wennunevictableoderfile_mappedgroß ist (wie bei gesperrtem Arbeitsspeicher oder speicherzugeordneten Datenbanken), wird dieser Speicherbedarf ebenfalls als Arbeitssatz gezählt. - Von anonymen Heaps dominierte oder bursty Dienste (
anonmacht den Großteil vonmemory.currentaus, z. B. Netzwerk- oder Konnektivitätsstacks): Dimensioniereandroid:maxMbso, dass P90 bis P95 vonmemory.peakabgedeckt werden, damit vorübergehende Bursts keine synchrone ZRAM-Auslagerung auf latenzempfindlichen Threads auslösen. - Display-intensive UI-Prozesse (z. B. Auswahl, Galerien oder System-UI-Hilfsprogramme): Miss den nicht-UI-bezogenen Baseline-Speicherbedarf und den inkrementellen Bitmap-Speicherbedarf pro Megapixel auf deinen Displays mit der niedrigsten und höchsten Auflösung und gib das Budget mit
android:maxMbplusandroid:additionalBytesPerDisplayPixelan.
„memory.high“ auf physischen Geräten bereinigen
Bevor du ein neues Budget für den Build festlegst, verwende am memory-limiter manual (oder schreibe bei einem Userdebug-Build direkt in den memory.high-cgroup-Knoten des Prozesses), um mögliche Limits zu testen, während du deine automatisierten CUJ-Tests ausführst:
# Override memory.high for a specific PID to 60 MB during a CUJ test
adb shell am memory-limiter manual <PID> 60MBBeobachten Sie memory.stat und memory.events vor und nach der CUJ:
- Budget ist zu niedrig:
workingset_refault_anonoderworkingset_refault_filesteigt während der CUJ stark an,memory.swap.currentwächst während der aktiven Arbeit im Vordergrund kontinuierlich oder die CUJ-Frame- oder Binder-Latenz verschlechtert sich. Erhöhen Sie das Budget über den Refault-Knick hinaus. - Budget ist zu hoch:
memory.events:highbleibt auch nach hoher Arbeitslast durch Dateistreaming oder Indexierung bei0undfileinmemory.statwächst unbegrenzt mit kalten Seiten. Reduzieren Sie das Budget auf den aktiven Arbeitssatz. - Budget ist gut kalibriert: Der stabile Zustand
memory.currentbleibt im Normalbetrieb untermemory.high. Bei starken Datei- oder Cache-Bursts werden vonpgstealkalte Dateiseiten zurückgefordert, währendworkingset_refault_anonnahe null bleibt und die CUJ-Latenz unverändert ist.