Arbeitsspeicherbegrenzer

Android 17 und höher umfasst den Memory Limiter, einen Systemdienst, der die Arbeitsspeichernutzung von Anwendungsprozessen mithilfe von Linux cgroup v2 überwacht und begrenzt. Der Memory Limiter verhindert, dass einzelne Apps unverhältnismäßig viel Systemspeicher verbrauchen. Dadurch wird der Gesamtspeicherdruck reduziert und aggressive Low-Memory-Kills von kritischen Vordergrund- und im Cache gespeicherten Prozessen verhindert.

Mechanismus

Der Memory Limiter ist in den Activity Manager Service (AMS) integriert, um Ereignisse im Lebenszyklus von Prozessen und Statusänderungen zu verfolgen. Der Memory Limiter erzwingt Speicherlimits mithilfe der Linux-Kernel-cgroup-v2-Hierarchie.

Damit der Memory Limiter funktioniert, müssen im Kernel des Geräts cgroup v2 und der memory-Controller aktiviert sein. Der Dienst basiert speziell auf den folgenden Attributen:

memory.high
Ein weiches Limit. Wenn es überschritten wird, wird der Prozess gedrosselt und der Kernel versucht, Seiten proaktiv aus diesem Prozess freizugeben.
memory.swap.max
Begrenzt die maximale Menge an Auslagerungsspeicher (z. B. ZRAM), die der Prozess verwenden kann.

Auswirkungen auf Apps

Apps, die innerhalb ihrer Speicherlimits arbeiten, sind vom Memory Limiter nicht betroffen.

Wenn eine App das Limit memory.high überschreitet, gibt der Kernel den sauberen, dateibasierten Speicher der App frei und lagert den inaktiven anonymen Speicher aus, damit die App innerhalb des Limits bleibt. Aufgrund dieser Seitenfreigabe und Auslagerungsaktivität kann es zu einer vorübergehenden Drosselung der Ausführung der App kommen, sie wird aber weiterhin ausgeführt.

In Extremsituationen, z. B. bei einem unkontrollierten Speicherleck, bei dem die App weiterhin anonymen Speicher zuweist, nachdem der Auslagerungsspeicher erschöpft ist, kann die App keinen Speicher zuweisen und wird beendet.

Prozessmonitoring

Der Memory Limiter überwacht standardmäßig App-Prozesse (UID >= 10000). Kernsystemprozesse sind ausgenommen, um die grundlegende Systemstabilität zu gewährleisten.

Der Memory Limiter weist Speicherlimits basierend auf dem Sichtbarkeitsstatus des Prozesses zu:

  • Sichtbare Prozesse:Prozesse, die eine für Nutzer sichtbare UI hosten (z. B. die derzeit fokussierte Aktivität oder eine aktive Interaktion). Da für die Anzeige einer UI ein größerer Arbeitssatz für Rendering- und Grafikpipelines erforderlich ist, erhalten sichtbare Prozesse ein großzügigeres Speicherlimit.

  • Nicht sichtbare Prozesse:Prozesse, die im Hintergrund arbeiten, ohne eine UI zu präsentieren (z. B. Hintergrunddienste oder Broadcast-Empfänger). Da sie keine UI-Komponenten rendern, unterliegen sie einem restriktiveren Limit.

In der folgenden Tabelle werden bestimmte Prozessstatus den Klassifizierungen für Speicherlimits zugeordnet:

ProzessstatusArbeitsspeicherlimit
PERSISTENTUneingeschränkt
PERSISTENT_UIUneingeschränkt
TOPSichtbar
BOUND_TOPSichtbar
FOREGROUND_SERVICENicht sichtbar
BOUND_FOREGROUND_SERVICENicht sichtbar
IMPORTANT_FOREGROUNDSichtbar
IMPORTANT_BACKGROUNDNicht sichtbar
TRANSIENT_BACKGROUNDNicht sichtbar
BACKUPNicht sichtbar
SERVICENicht sichtbar
RECEIVERNicht sichtbar
TOP_SLEEPINGSichtbar
HEAVY_WEIGHTNicht sichtbar
HOMENicht sichtbar
LAST_ACTIVITYNicht sichtbar
CACHED_ACTIVITYIm Cache
CACHED_ACTIVITY_CLIENTIm Cache
CACHED_RECENTIm Cache
CACHED_EMPTYIm Cache

Im Cache-Status werden Prozesse eingefroren und dann maximal freigegeben.

Wenn ein Prozess das zugewiesene Limit memory.high überschreitet, erkennt der Memory Limiter das Ereignis und kann Diagnoseaktionen auslösen, z. B. ein Speicherprofil erfassen oder eine Anomalie in statsd protokollieren.

Konfiguration

In Android 17 und höher wird der Memory Limiter mit der Standardkonfigurationsdatei der Plattform auf der Systempartition unter /system/etc/memory-limiter-config.xml konfiguriert.

XML-Format

Die Konfigurationsdatei folgt dem in memory-limiter-config.xsd definierten Schema. Die Datei definiert mehrere Limitsätze, die nach verfügbarem Speicher sortiert sind. Der Dienst wählt den höchsten übereinstimmenden Limitsatz basierend auf dem verfügbaren RAM des Geräts aus. Alle Speicherwerte werden in Mebibyte (MiB) angegeben.

<MemoryLimiterConfig>
  <version>1</version>
  <configList>
    <!-- RAM boundaries account for MemTotal excluding carve-outs. -->
    <limitSet>
      <!-- Limits for 16GB RAM device [14336, 18432): 10G/5G/5G/5G -->
      <minimumRequiredMemTotal>14336</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>18432</maximumAllowedMemTotal>
      <memVisible>10240</memVisible>
      <memNotVisible>5120</memNotVisible>
      <swapVisible>5120</swapVisible>
      <swapNotVisible>5120</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 12GB RAM device [9216, 14336): 8G/4G/4G/4G -->
      <minimumRequiredMemTotal>9216</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>14336</maximumAllowedMemTotal>
      <memVisible>8192</memVisible>
      <memNotVisible>4096</memNotVisible>
      <swapVisible>4096</swapVisible>
      <swapNotVisible>4096</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 8GB RAM device [6800, 9216): 5G/3G/3G/3G -->
      <minimumRequiredMemTotal>6800</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>9216</maximumAllowedMemTotal>
      <memVisible>5120</memVisible>
      <memNotVisible>3072</memNotVisible>
      <swapVisible>3072</swapVisible>
      <swapNotVisible>3072</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 6GB RAM device [4800, 6800): 4G/2G/2G/2G -->
      <minimumRequiredMemTotal>4800</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>6800</maximumAllowedMemTotal>
      <memVisible>4096</memVisible>
      <memNotVisible>2048</memNotVisible>
      <swapVisible>2048</swapVisible>
      <swapNotVisible>2048</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 4GB RAM device [3200, 4800): 2G/1G/1G/1G -->
      <minimumRequiredMemTotal>3200</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>4800</maximumAllowedMemTotal>
      <memVisible>2048</memVisible>
      <memNotVisible>1024</memNotVisible>
      <swapVisible>1024</swapVisible>
      <swapNotVisible>1024</swapNotVisible>
    </limitSet>
  </configList>
</MemoryLimiterConfig>
version
Eine positive Ganzzahl, die die Konfigurationsversion identifiziert. Sie muss 1 sein.
minimumRequiredMemTotal

Das minimale System MemTotal (in MiB, einschließlich Untergrenze), das für die Anwendung dieses Limitsatzes erforderlich ist. Der Dienst vergleicht diesen Wert mit dem vom Kernel in /proc/meminfo gemeldeten gesamten verfügbaren Speicher.

Informationen zu Speicherausnahmen und -grenzen:Der Kernelwert MemTotal gibt den physischen DRAM an, auf den das Betriebssystem zugreifen kann, nachdem die Speicherausnahmen für Hardware und Firmware abgezogen wurden. Durch Ausnahmen wird RAM für spezielle Hardwarekomponenten wie die GPU, das Baseband-Modem, den Kamera-ISP, sichere Ausführungsumgebungen und Hypervisoren reserviert. In der Regel werden zwischen 500 MiB und über 2 GiB physischer Speicher verbraucht.

Da MemTotal niedriger ist als der angegebene physische DRAM des Geräts, minimumRequiredMemTotal Schwellenwerte enthalten einen Spielraum für diese Ausnahmen. Beispielsweise wird in einer Konfiguration für Geräte mit 16 GB eine Untergrenze von 14336 (14 GiB) anstelle von 16384 (16 GiB) angegeben und für Geräte mit 12 GB eine Untergrenze von 9216 (9 GiB). So wird sichergestellt, dass jedes Gerät unabhängig von den Ausnahmen auf Boardebene der vorgesehenen Stufe entspricht.

maximumAllowedMemTotal

Das maximale System MemTotal (in MiB, ohne Obergrenze), das für die Anwendung dieses Limitsatzes erforderlich ist. Wenn MemTotal größer oder gleich dieser Obergrenze ist, wird der Limitsatz nicht angewendet. Bei Geräten mit 18.432 MiB oder mehr oder bei Geräten mit weniger als 3.200 MiB wendet der Memory Limiter keine Limits an, sodass Prozesse nicht eingeschränkt werden.

memVisible

Das weiche Speicherlimit (memory.high) in MiB, das auf sichtbare Prozesse angewendet wird.

memNotVisible

Das weiche Speicherlimit (memory.high) in MiB, das auf nicht sichtbare Prozesse angewendet wird.

swapVisible

Das Auslagerungsspeicherlimit (memory.swap.max) in MiB, das auf sichtbare Prozesse angewendet wird.

swapNotVisible

Das Auslagerungsspeicherlimit (memory.swap.max) in MiB, das auf nicht sichtbare Prozesse angewendet wird.

Standardlimits der Plattform

In Android 17 und höher gibt die Plattform Standardlimits für Speicher und Auslagerungsspeicher in /system/etc/memory-limiter-config.xml für gängige physische RAM Stufen mit [floor, ceiling) Bereichsintervallen an:

Ziel-RAM MemTotal-Bereich [Untergrenze, Obergrenze)
(minimumRequiredMemTotal,
maximumAllowedMemTotal)
Speicherlimit für sichtbare Prozesse
(memVisible)
Speicherlimit für nicht sichtbare Prozesse
(memNotVisible)
Auslagerungsspeicherlimit für sichtbare Prozesse
(swapVisible)
Auslagerungsspeicherlimit für nicht sichtbare Prozesse
(swapNotVisible)
16 GB [14336, 18432) MiB 10.240 MiB (10 GiB) 5.120 MiB (5 GiB) 5.120 MiB (5 GiB) 5.120 MiB (5 GiB)
12 GB [9216, 14336) MiB 8.192 MiB (8 GiB) 4.096 MiB (4 GiB) 4.096 MiB (4 GiB) 4.096 MiB (4 GiB)
8 GB [6800, 9216) MiB 5.120 MiB (5 GiB) 3.072 MiB (3 GiB) 3.072 MiB (3 GiB) 3.072 MiB (3 GiB)
6 GB [4800, 6800) MiB 4.096 MiB (4 GiB) 2.048 MiB (2 GiB) 2.048 MiB (2 GiB) 2.048 MiB (2 GiB)
4 GB [3200, 4800) MiB 2.048 MiB (2 GiB) 1.024 MiB (1 GiB) 1.024 MiB (1 GiB) 1.024 MiB (1 GiB)

Prinzipien für Speicherlimits

Die Memory Limiter-Konfiguration basiert auf den folgenden Plattformprinzipien:

  • Konsistenz des Ökosystems und App-Kompatibilität:Einheitliche Speicherlimits auf allen Geräten sorgen für eine vorhersehbare App-Leistung im gesamten Android-Ökosystem. Apps werden anhand von Standardspeichererwartungen entwickelt und getestet. Durch einheitliche Plattformlimits werden unerwartete Drosselungen oder vorzeitige Beendigungen vermieden.

  • Proportionale Ressourcenzuweisung:Die Plattformlimits werden entsprechend der physischen RAM-Kapazität kalibriert:

    • Sichtbare Prozesse:Etwa 1/2 bis 2/3 des gesamten physischen RAM werden zugewiesen, um aktive UI-, Kompositions- und Rendering-Arbeitslasten zu unterstützen.
    • Nicht sichtbare Prozesse:Etwa 1/4 bis 1/3 des gesamten physischen RAM werden für Hintergrundaufgaben zugewiesen.
  • Universelle Anwendbarkeit:Die Limits gelten einheitlich für alle Anwendungsprozesse auf dem Gerät (UID >= 10000), einschließlich vorinstallierter System- und OEM-Apps. Der Memory Limiter unterstützt keine Zulassungslisten, um bestimmte Apps auszunehmen. So wird eine faire Speicherverwaltung für alle Apps gewährleistet.

  • Keine APIs für Laufzeitabfragen:In Android 17 und höher können Apps ihre zugewiesenen Speicherlimits nicht programmatisch zur Laufzeit abfragen. Die Standardlimits der Plattform sind mit großzügigen Spielräumen definiert, sodass gut funktionierende Apps bei normaler Nutzung ohne Einschränkungen ausgeführt werden können.

  • Kernel-Freigabe und Auslagerungsdynamik:Wenn ein App-Prozess das Limit memory.high erreicht, richtet der Linux-Kernel die Speicherfreigabe speziell auf diese Prozess-Cgroup aus. Dazu gehört das Löschen inaktiver dateibasierter Seiten und das Auslagern kalter anonymer Seiten in ZRAM, wodurch die Systemflüssigkeit erhalten bleibt, ohne den globalen Speicherdruck zu erhöhen.

Shell-Befehle

Mit dem Befehl am memory-limiter können Entwickler und Systemintegratoren zur Laufzeit mit dem Dienst interagieren, um ihn zu entwickeln, zu testen und Fehler zu beheben:

am memory-limiter <SUB-COMMAND>

Status

Der Unterbefehl status meldet den Betriebsstatus und die aktiven Messwerte des Memory Limiter:

adb shell am memory-limiter status

Hier ist ein Beispiel für die Ausgabe:

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

Wichtige Felder in der Ausgabe sind:

monitoring
Gibt an, ob der Memory Limiter Prozesse aktiv überwacht.
visibleMem und notVisibleMem
Die berechneten absoluten Speicherlimits (memory.high), die derzeit für jeden Sichtbarkeitsstatus erzwungen werden.
visibleSwap und notVisibleSwap
Die berechneten absoluten Auslagerungsspeicherlimits (memory.swap.max), die derzeit für jeden Sichtbarkeitsstatus erzwungen werden.
events
Die Anzahl der Male, die ein Prozess sein zugewiesenes Limit überschritten hat.
processes
Die aktuelle Anzahl der überwachten Prozesse.

Ignorieren

Mit dem Unterbefehl ignore wird eine bestimmte UID oder alle Prozesse vorübergehend von der Speicherbegrenzung ausgeschlossen. Dies ist nützlich für Leistungsbenchmarks, Stresstests oder die Diagnose des Speicherverhaltens:

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

Manuell

Mit dem Unterbefehl manual werden die berechneten Limits für einen bestimmten Prozess anhand der Prozess-ID (PID) mit einem benutzerdefinierten absoluten Wert in Byte überschrieben. Der Wert muss eine Ganzzahl sein, kann aber das Suffix MB für MiB oder das Suffix GB für GiB enthalten:

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

Manuelle Überschreibungen gelten nur für die Lebensdauer dieser bestimmten Prozessinstanz. Wenn der Prozess neu gestartet wird, werden die Standardlimits basierend auf dem Status wiederhergestellt.

Eine manuelle Überschreibung darf die physischen Systemlimits nicht überschreiten.