Ogranicznik pamięci

Android 17 i nowsze wersje zawierają ogranicznik pamięci, czyli usługę systemową, która monitoruje i ogranicza wykorzystanie pamięci przez procesy aplikacji za pomocą cgroup v2 w systemie Linux. Ogranicznik pamięci uniemożliwia poszczególnym aplikacjom zużywanie nieproporcjonalnie dużej ilości pamięci systemowej, co zmniejsza ogólne obciążenie pamięci i zapobiega agresywnemu zamykaniu przez mechanizm low-memory killer (lmkd) krytycznych procesów działających na pierwszym planie i procesów w pamięci podręcznej.

Mechanizm

Ogranicznik pamięci jest zintegrowany z usługą Activity Manager Service (AMS), aby śledzić zdarzenia cyklu życia procesu i zmiany stanu. Ogranicznik pamięci egzekwuje limity pamięci za pomocą hierarchii cgroup v2 jądra systemu Linux.

Aby obsługiwać ogranicznik pamięci, jądro urządzenia musi mieć włączoną cgroup v2 i kontroler memory. Usługa opiera się w szczególności na tych atrybutach:

memory.high
Limit. Po jego przekroczeniu proces jest ograniczany, a jądro próbuje aktywnie odzyskać strony z tego procesu.
memory.swap.max
Ogranicza maksymalną ilość miejsca wymiany (np. ZRAM), jaką może wykorzystać proces.

Wpływ na aplikacje

Ogranicznik pamięci nie ma wpływu na aplikacje, które działają w ramach limitów pamięci.

Gdy aplikacja przekroczy limit memory.high, jądro usunie z pamięci czyste strony aplikacji i przeniesie do pamięci wymiany nieaktywne strony anonimowe, aby utrzymać aplikację w limicie. W wyniku tego usunięcia stron i aktywności związanej z pamięcią wymiany aplikacja może tymczasowo doświadczyć ograniczenia wykonywania, ale będzie nadal działać.

W ekstremalnych sytuacjach, takich jak niekontrolowany wyciek pamięci, gdy aplikacja nadal przydziela pamięć anonimową po wyczerpaniu miejsca wymiany, aplikacja nie może przydzielić pamięci i zostaje zamknięta.

Monitorowanie procesów

Ogranicznik pamięci domyślnie monitoruje procesy aplikacji (UID >= 10000). Procesy podstawowe systemu są wyłączone, aby zachować stabilność systemu.

Ogranicznik pamięci przypisuje limity pamięci na podstawie stanu widoczności procesu:

  • Widoczne procesy: procesy hostujące interfejs widoczny dla użytkownika (np. aktualnie aktywny ekran lub aktywna interakcja). Ponieważ wyświetlanie interfejsu wymaga większego zestawu roboczego do renderowania i potoków graficznych, widoczne procesy otrzymują większy limit pamięci.

  • Niewidoczne procesy: procesy wykonujące pracę w tle bez wyświetlania interfejsu (np. usługi działające w tle lub odbiorniki transmisji). Ponieważ nie renderują one komponentów interfejsu, działają w ramach bardziej restrykcyjnego limitu.

W tabeli poniżej przedstawiono mapowanie konkretnych stanów procesu na klasyfikacje limitów pamięci:

Stan procesuLimit pamięci
PERSISTENTBez ograniczeń
PERSISTENT_UIBez ograniczeń
TOPWidoczne
BOUND_TOPWidoczne
FOREGROUND_SERVICENiewidoczne
BOUND_FOREGROUND_SERVICENiewidoczne
IMPORTANT_FOREGROUNDWidoczne
IMPORTANT_BACKGROUNDNiewidoczne
TRANSIENT_BACKGROUNDNiewidoczne
BACKUPNiewidoczne
SERVICENiewidoczne
RECEIVERNiewidoczne
TOP_SLEEPINGWidoczne
HEAVY_WEIGHTNiewidoczne
HOMENiewidoczne
LAST_ACTIVITYNiewidoczne
CACHED_ACTIVITYW pamięci podręcznej
CACHED_ACTIVITY_CLIENTW pamięci podręcznej
CACHED_RECENTW pamięci podręcznej
CACHED_EMPTYW pamięci podręcznej

W stanie w pamięci podręcznej procesy są zamrożone, a następnie maksymalnie odzyskane.

Gdy proces przekroczy przypisany limit memory.high, ogranicznik pamięci wykryje to zdarzenie i może wywołać działania diagnostyczne, takie jak przechwycenie profilu pamięci lub zalogowanie anomalii w statsd.

Konfiguracja

W Androidzie 17 i nowszych wersjach ogranicznik pamięci jest konfigurowany za pomocą standardowego pliku konfiguracji platformy w partycji systemowej w lokalizacji /system/etc/memory-limiter-config.xml.

Format XML

Plik konfiguracji jest zgodny ze schematem zdefiniowanym w memory-limiter-config.xsd. Plik definiuje wiele zestawów limitów uporządkowanych według dostępnej pamięci. Usługa wybiera najwyższy pasujący zestaw limitów na podstawie dostępnej pamięci RAM urządzenia. Wszystkie wartości pamięci są podawane w mebibajtach (MiB).

<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
Dodatnia liczba całkowita identyfikująca wersję konfiguracji. Musi to być 1.
minimumRequiredMemTotal

Minimalna ilość pamięci systemowej MemTotal (w MiB, włącznie z dolną granicą) wymagana do zastosowania tego zestawu limitów. Usługa porównuje tę wartość z łączną dostępną pamięcią zgłoszoną przez jądro w /proc/meminfo.

Informacje o wyłączeniach i granicach pamięci: wartość MemTotal jądra odzwierciedla fizyczną pamięć DRAM dostępną dla systemu operacyjnego po odjęciu wyłączeń pamięci sprzętu i oprogramowania układowego. Wyłączenia rezerwują pamięć RAM dla wyspecjalizowanych komponentów sprzętowych, takich jak GPU, modem pasma podstawowego, ISP aparatu, bezpieczne środowiska wykonawcze i hipernadzorcy, zwykle zużywając od 500 MiB do ponad 2 GiB pamięci fizycznej.

Ponieważ MemTotal jest mniejsza niż deklarowana fizyczna pamięć DRAM urządzenia, minimumRequiredMemTotal progi obejmują margines na te wyłączenia. Na przykład konfiguracja przeznaczona dla urządzeń z 16 GB pamięci określa dolną granicę 14336 (14 GiB), a nie 16384 (16 GiB), a warstwa dla urządzeń z 12 GB pamięci określa dolną granicę 9216 (9 GiB). Dzięki temu każde urządzenie pasuje do zamierzonej warstwy niezależnie od różnic w wyłączeniach na poziomie płyty.

maximumAllowedMemTotal

Maksymalna ilość pamięci systemowej MemTotal (w MiB, bez górnej granicy) wymagana do zastosowania tego zestawu limitów. Jeśli MemTotal jest większa lub równa tej górnej granicy, zestaw limitów nie jest stosowany. W przypadku urządzeń z 18 432 MiB lub więcej albo z mniej niż 3200 MiB ogranicznik pamięci nie stosuje limitów, pozostawiając procesy bez ograniczeń.

memVisible

Limit pamięci (memory.high) w MiB stosowany do widocznych procesów.

memNotVisible

Limit pamięci (memory.high) w MiB stosowany do niewidocznych procesów.

swapVisible

Limit miejsca wymiany (memory.swap.max) w MiB stosowany do widocznych procesów.

swapNotVisible

Limit miejsca wymiany (memory.swap.max) w MiB stosowany do niewidocznych procesów.

Standardowe limity platformy

W Androidzie 17 i nowszych wersjach platforma określa standardowe limity pamięci i miejsca wymiany w /system/etc/memory-limiter-config.xml w przypadku typowych warstw fizycznej pamięci RAM za pomocą [floor, ceiling) przedziałów:

Docelowa fizyczna pamięć RAM Zakres MemTotal [dolna granica, górna granica)
(minimumRequiredMemTotal,
maximumAllowedMemTotal)
Limit pamięci widocznej
(memVisible)
Limit pamięci niewidocznej
(memNotVisible)
Limit miejsca wymiany widocznego
(swapVisible)
Limit miejsca wymiany niewidocznego
(swapNotVisible)
16 GB [14336, 18432) MiB 10 240 MiB (10 GiB) 5120 MiB (5 GiB) 5120 MiB (5 GiB) 5120 MiB (5 GiB)
12 GB [9216, 14336) MiB 8192 MiB (8 GiB) 4096 MiB (4 GiB) 4096 MiB (4 GiB) 4096 MiB (4 GiB)
8 GB [6800, 9216) MiB 5120 MiB (5 GiB) 3072 MiB (3 GiB) 3072 MiB (3 GiB) 3072 MiB (3 GiB)
6 GB [4800, 6800) MiB 4096 MiB (4 GiB) 2048 MiB (2 GiB) 2048 MiB (2 GiB) 2048 MiB (2 GiB)
4 GB [3200, 4800) MiB 2048 MiB (2 GiB) 1024 MiB (1 GiB) 1024 MiB (1 GiB) 1024 MiB (1 GiB)

Zasady dotyczące limitów pamięci

Konfiguracja ogranicznika pamięci jest oparta na tych zasadach platformy:

  • Spójność ekosystemu i zgodność aplikacji: spójne limity pamięci na różnych urządzeniach zapewniają przewidywalną wydajność aplikacji w całym ekosystemie Androida. Aplikacje są tworzone i testowane pod kątem standardowych oczekiwań dotyczących pamięci. Utrzymanie jednolitych limitów platformy pozwala uniknąć nieoczekiwanego ograniczania lub przedwczesnego zamykania.

  • Proporcjonalne przydzielanie zasobów: limity platformy są kalibrowane zgodnie z pojemnością fizycznej pamięci RAM:

    • Widoczne procesy: przydzielono im około 1/2 do 2/3 łącznej fizycznej pamięci RAM, aby obsługiwać aktywne interfejsy, kompozycje i obciążenia związane z renderowaniem.
    • Niewidoczne procesy: przydzielono im około 1/4 do 1/3 łącznej fizycznej pamięci RAM na potrzeby zadań w tle.
  • Uniwersalne zastosowanie: limity obowiązują jednolicie wszystkie procesy aplikacji na urządzeniu (UID >= 10000), w tym preinstalowane aplikacje systemowe i aplikacje OEM. Ogranicznik pamięci nie obsługuje list dozwolonych, aby wykluczyć określone aplikacje, co zapewnia sprawiedliwe zarządzanie pamięcią we wszystkich aplikacjach.

  • Brak interfejsów API zapytań w czasie działania: w Androidzie 17 i nowszych wersjach aplikacje nie mogą programowo wysyłać zapytań o przypisane limity pamięci w czasie działania. Standardowe limity platformy są zdefiniowane z dużym marginesem, dzięki czemu dobrze działające aplikacje działają bez ograniczeń podczas normalnego użytkowania.

  • Odzyskiwanie pamięci przez jądro i dynamika miejsca wymiany: gdy proces aplikacji zbliża się do limitu memory.high, jądro systemu Linux kieruje odzyskiwanie pamięci konkretnie do cgroup tego procesu. Obejmuje to usuwanie nieaktywnych stron z plikami i przenoszenie nieużywanych stron anonimowych do ZRAM, co zapewnia płynność systemu bez obciążania globalnej pamięci.

Polecenia powłoki

Polecenie am memory-limiter umożliwia deweloperom i integratorom systemów interakcję z usługą w czasie działania na potrzeby tworzenia, testowania i debugowania:

am memory-limiter <SUB-COMMAND>

status

Podpolecenie status zgłasza stan operacyjny i aktywne dane ogranicznika pamięci:

adb shell am memory-limiter status

Oto przykładowe dane wyjściowe:

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

Kluczowe pola w danych wyjściowych:

monitoring
Wskazuje, czy ogranicznik pamięci aktywnie obserwuje procesy.
visibleMem i notVisibleMem
Obliczone limity bezwzględne pamięci (memory.high) obecnie egzekwowane dla każdego stanu widoczności.
visibleSwap i notVisibleSwap
Obliczone limity bezwzględne miejsca wymiany (memory.swap.max) obecnie egzekwowane dla każdego stanu widoczności.
events
Liczba przypadków, w których proces przekroczył przypisany limit.
processes
Aktualna liczba monitorowanych procesów.

ignoruj

Podpolecenie ignore tymczasowo wyklucza z ograniczania pamięci określony UID lub wszystkie procesy. Jest to przydatne do testowania wydajności, testowania obciążenia lub diagnozowania zachowania pamięci:

# 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

ręcznie

Podpolecenie manual zastępuje obliczone limity dla określonego procesu według identyfikatora PID niestandardową wartością bezwzględną w bajtach. Wartość musi być liczbą całkowitą, ale może zawierać sufiks MB, aby wskazać MiB, lub sufiks GB, aby wskazać GiB:

# 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

Ręczne zastąpienia obowiązują tylko przez cały okres istnienia konkretnej instancji procesu. Jeśli proces zostanie ponownie uruchomiony, powróci do standardowych limitów na podstawie swojego stanu.

Ręczne zastąpienie nie może przekraczać fizycznych limitów systemu.