Arbeitsspeicherbegrenzer

Android 17 und höher enthält den Memory Limiter, einen Systemdienst, der die Arbeitsspeichernutzung von Anwendungsprozessen mithilfe von Linux cgroup v2 überwacht und einschränkt. Der Memory Limiter verhindert, dass einzelne Apps unverhältnismäßig viel Systemspeicher verbrauchen. Dadurch wird die Speicherauslastung insgesamt reduziert und es wird verhindert, dass wichtige Vordergrund- und im Cache gespeicherte Prozesse bei Speichermangel aggressiv beendet werden.

Mechanismus

Der Memory Limiter ist in den Activity Manager Service (AMS) eingebunden, um Prozesslebenszyklus-Ereignisse und Statusänderungen zu verfolgen. Der Memory Limiter erzwingt Arbeitsspeicherlimits mithilfe der cgroup v2-Hierarchie des Linux-Kernels.

Zur Unterstützung des Memory Limiters müssen im Gerätekernel cgroup v2 und der Controller memory aktiviert sein. Der Dienst verwendet insbesondere die folgenden Attribute:

memory.high
Ein weiches Limit. Wenn dieser Wert überschritten wird, wird der Prozess gedrosselt und der Kernel versucht, proaktiv Seiten aus diesem Prozess zurückzufordern.
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 ausgeführt werden, sind vom Memory Limiter nicht betroffen.

Wenn eine App das memory.high-Limit überschreitet, entfernt der Kernel den bereinigten dateigestützten Speicher der App und lagert den inaktiven anonymen Speicher aus, um die App innerhalb des Limits zu halten. Aufgrund dieser Seitenentfernungs- und Auslagerungsaktivität kann es zu einer vorübergehenden Drosselung der Ausführung der Anwendung kommen, sie wird jedoch weiterhin ausgeführt.

In extremen Situationen, z. B. bei einem unkontrollierten Speicherleck, bei dem die App nach Erschöpfung des Auslagerungsspeichers weiterhin anonymen Speicher zuweist, kann die App keinen Speicher zuweisen und wird beendet.

Prozessmonitoring

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

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

  • Sichtbare Prozesse:Prozesse, die eine für den Nutzer sichtbare Benutzeroberfläche hosten, z. B. die aktuell fokussierte Aktivität oder eine aktive Interaktion. Da für die Anzeige einer Benutzeroberfläche 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 ausgeführt werden, ohne eine Benutzeroberfläche anzuzeigen (z. B. Hintergrunddienste oder Übertragungsempfänger). Da sie keine UI-Komponenten rendern, unterliegen sie einem restriktiveren Limit.

In der folgenden Tabelle werden bestimmte Prozessstatus den Klassifizierungen für Arbeitsspeicherlimits 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 memory.high-Limit überschreitet, erkennt der Memory Limiter das Ereignis und kann Diagnoseaktionen auslösen, z. B. das Erfassen eines Speicherprofils oder das Protokollieren einer Anomalie in statsd.

Konfiguration

In Android 17 und höher wird der Memory Limiter über die 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. In der Datei werden mehrere Limitsätze definiert, die nach verfügbarem Arbeitsspeicher sortiert sind. Der Dienst wählt den höchsten passenden Limitsatz basierend auf dem verfügbaren RAM des Geräts aus. Alle Arbeitsspeicherwerte 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. Dies muss 1 sein.
minimumRequiredMemTotal

Die Mindest-MemTotal des Systems (in MiB, einschließlich Untergrenze), die erforderlich ist, damit dieser Limitsatz angewendet wird. Der Dienst vergleicht dies mit dem gesamten verfügbaren Arbeitsspeicher, der vom Kernel in /proc/meminfo gemeldet wird.

Informationen zu Speicherreservierungen und ‑grenzen: Der Kernel-Wert MemTotal spiegelt den physischen DRAM wider, auf den das Betriebssystem zugreifen kann, nachdem Speicherreservierungen für Hardware und Firmware abgezogen wurden. Durch Carve-outs 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 dabei zwischen 500 MiB und über 2 GiB physischer Speicher verbraucht.

Da MemTotal niedriger ist als der angegebene physische DRAM des Geräts, enthalten die minimumRequiredMemTotal-Grenzwerte eine Toleranz für diese Ausnahmen. Eine Konfiguration für 16-GB-Geräte gibt beispielsweise eine Untergrenze von 14336 (14 GiB) anstelle von 16384 (16 GiB) an und eine Stufe für 12-GB-Geräte gibt eine Untergrenze von 9216 (9 GiB) an. So wird sichergestellt, dass jedes Gerät unabhängig von Abweichungen bei der Board-Ebene der vorgesehenen Stufe entspricht.

maximumAllowedMemTotal

Der maximale System-MemTotal (in MiB, exklusive Obergrenze), für den dieser Grenzwert gilt. Wenn MemTotal größer oder gleich dieser Obergrenze ist, gilt das festgelegte Limit nicht. 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 Arbeitsspeicherlimit (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 Limit für den Auslagerungsspeicher (memory.swap.max) in MiB, das auf sichtbare Prozesse angewendet wird.

swapNotVisible

Das Limit für den Auslagerungsspeicher (memory.swap.max) in MiB, das auf nicht sichtbare Prozesse angewendet wird.

Standardlimits für die Plattform

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

Physischer Ziel-RAM MemTotal-Bereich [Untergrenze, Obergrenze)
(minimumRequiredMemTotal,
maximumAllowedMemTotal)
Sichtbares Arbeitsspeicherlimit
(memVisible)
Nicht sichtbares Speicherlimit
(memNotVisible)
Sichtbares Swap-Limit
(swapVisible)
Limit für nicht sichtbare Swaps
(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)

Grundsätze für Arbeitsspeicherlimits

Die Konfiguration des Memory Limiters basiert auf den folgenden Plattformprinzipien:

  • Konsistenz im Ökosystem und App-Kompatibilität:Einheitliche Arbeitsspeicherlimits auf allen Geräten sorgen für eine vorhersehbare App-Leistung im gesamten Android-Ökosystem. Apps werden gemäß den standardmäßigen Erwartungen an den Arbeitsspeicher entwickelt und getestet. Durch die Beibehaltung einheitlicher Plattformlimits werden unerwartete Drosselungen oder vorzeitige Beendigungen vermieden.

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

    • Sichtbare Prozesse:Etwa die Hälfte bis zwei Drittel des gesamten physischen RAMs werden für aktive UI-, Kompositions- und Rendering-Arbeitslasten zugewiesen.
    • Nicht sichtbare Prozesse:Ungefähr 1/4 bis 1/3 des gesamten physischen RAM wird 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. Informationen zum Konfigurieren von selbstoptimierenden memory.min-Schutzuntergrenzen für kritische interaktive Daemons und strengeren, auf die Arbeitslast abgestimmten memory.high-Obergrenzen für einzelne System-Apps und -Dienste finden Sie unter Systemspeicherbudgets.

  • Keine Laufzeitabfrage-APIs:Ab Android 17 können Apps ihre zugewiesenen Arbeitsspeicherlimits zur Laufzeit nicht mehr programmatisch abfragen. Die Standardplattformlimits sind großzügig bemessen, sodass gut funktionierende Apps bei normaler Nutzung ohne Einschränkungen funktionieren.

  • Kernel-Rückgewinnung und Auslagerungsdynamik:Wenn sich ein App-Prozess seinem memory.high-Limit nähert, richtet der Linux-Kernel die Speicherrückgewinnung speziell auf diese Prozess-cgroup aus. Dazu gehört das Verwerfen inaktiver dateibasierter Seiten und das Auslagern kalter anonymer Seiten in den ZRAM, um die Systemleistung aufrechtzuerhalten, ohne den globalen Arbeitsspeicher zu belasten.

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 Limiters:

adb shell am memory-limiter status

Hier ein Beispiel für eine 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:

monitoring
Gibt an, ob der Memory Limiter aktiv Prozesse überwacht.
visibleMem und notVisibleMem
Die berechneten absoluten Arbeitsspeicherlimits (memory.high), die derzeit für jeden Sichtbarkeitsstatus erzwungen werden.
visibleSwap und notVisibleSwap
Die berechneten absoluten Swap-Limits (memory.swap.max), die derzeit für jeden Sichtbarkeitsstatus erzwungen werden.
events
Die Häufigkeit, mit der ein Prozess das ihm zugewiesene 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 Leistungs-Benchmarking, 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 nach 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 der jeweiligen Prozessinstanz. Wenn der Prozess neu gestartet wird, werden die Standardlimits basierend auf dem Status wiederhergestellt.

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