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 procesów działających na pierwszym planie i procesów w pamięci podręcznej z powodu braku pamięci.

Mechanizm

Ogranicznik pamięci integruje się z usługą Activity Manager (AMS), aby śledzić zdarzenia związane z cyklem ż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 korzysta z tych atrybutów:

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 jej nieaktywną pamięć anonimową, 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:

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

  • Procesy niewidoczne: procesy działające w tle bez wyświetlania interfejsu (np. usługi działające w tle lub odbiorniki transmisji). Ponieważ nie renderują 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ą zamrażane, a następnie maksymalnie odzyskiwane.

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 udostępnia standardowy plik konfiguracyjny na partycji systemowej, a jednocześnie obsługuje opcjonalne zastąpienia specyficzne dla urządzenia na partycji vendor:

  • Domyślna konfiguracja systemu: /system/etc/memory-limiter-config.xml
  • Zastąpienie dostawcy (opcjonalne): /vendor/etc/memory-limiter-config.xml

Ogranicznik pamięci najpierw sprawdza plik /vendor/etc/memory-limiter-config.xml. Jeśli jest obecny, używa konfiguracji dostawcy. W przeciwnym razie wraca do domyślnej konfiguracji systemu w pliku /system/etc/memory-limiter-config.xml.

Format XML

Plik konfiguracyjny 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 minimums account for MemTotal excluding carve-outs. -->
    <limitSet>
      <!-- Limits for 16GB RAM device (MemTotal >= 13GiB): 10G/5G/5G/5G -->
      <minimumRequiredMemTotal>13312</minimumRequiredMemTotal>
      <memVisible>10240</memVisible>
      <memNotVisible>5120</memNotVisible>
      <swapVisible>5120</swapVisible>
      <swapNotVisible>5120</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 12GB RAM device (MemTotal >= 10GiB): 8G/4G/4G/4G -->
      <minimumRequiredMemTotal>10240</minimumRequiredMemTotal>
      <memVisible>8192</memVisible>
      <memNotVisible>4096</memNotVisible>
      <swapVisible>4096</swapVisible>
      <swapNotVisible>4096</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 8GB RAM device (MemTotal >= 6.5GiB): 5G/3G/3G/3G -->
      <minimumRequiredMemTotal>6656</minimumRequiredMemTotal>
      <memVisible>5120</memVisible>
      <memNotVisible>3072</memNotVisible>
      <swapVisible>3072</swapVisible>
      <swapNotVisible>3072</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 6GB RAM device (MemTotal >= 4.5GiB): 4G/2G/2G/2G -->
      <minimumRequiredMemTotal>4608</minimumRequiredMemTotal>
      <memVisible>4096</memVisible>
      <memNotVisible>2048</memNotVisible>
      <swapVisible>2048</swapVisible>
      <swapNotVisible>2048</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 4GB RAM device (MemTotal >= 3GiB): 2G/1G/1G/1G -->
      <minimumRequiredMemTotal>3072</minimumRequiredMemTotal>
      <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 MemTotal (w MiB) wymagana do zastosowania tego zestawu limitów. Usługa porównuje tę wartość z łączną ilością dostępnej pamięci zgłoszoną przez jądro w pliku /proc/meminfo.

Informacje o wyłączeniach 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 1 GiB pamięci fizycznej.

Ponieważ MemTotal jest niższa niż reklamowana 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 wartość minimumRequiredMemTotal jako 13312 (13 GiB), a nie 16384 (16 GiB), a poziom dla urządzeń z 12 GB pamięci określa wartość 10240 (10 GiB). Dzięki temu każde urządzenie pasuje do zamierzonego poziomu niezależnie od różnic w wyłączeniach na poziomie płyty.

memVisible

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

memNotVisible

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

swapVisible

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

swapNotVisible

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

Standardowe limity platformy

W Androidzie 17 i nowszych wersjach platforma określa standardowe limity pamięci i miejsca wymiany w pliku /system/etc/memory-limiter-config.xml w przypadku typowych poziomów fizycznej pamięci RAM:

Docelowa fizyczna pamięć RAM Minimalna wymagana
pamięć MemTotal
(minimumRequiredMemTotal)
Limit pamięci widocznej
(memVisible)
Limit pamięci niewidocznej
(memNotVisible)
Limit miejsca wymiany widocznego
(swapVisible)
Limit miejsca wymiany niewidocznego
(swapNotVisible)
16 GB 13 312 MiB (13 GiB) 10 240 MiB (10 GiB) 5120 MiB (5 GiB) 5120 MiB (5 GiB) 5120 MiB (5 GiB)
12 GB 10 240 MiB (10 GiB) 8192 MiB (8 GiB) 4096 MiB (4 GiB) 4096 MiB (4 GiB) 4096 MiB (4 GiB)
8 GB 6656 MiB (6,5 GiB) 5120 MiB (5 GiB) 3072 MiB (3 GiB) 3072 MiB (3 GiB) 3072 MiB (3 GiB)
6 GB 4608 MiB (4,5 GiB) 4096 MiB (4 GiB) 2048 MiB (2 GiB) 2048 MiB (2 GiB) 2048 MiB (2 GiB)
4 GB 3072 MiB (3 GiB) 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:

    • Procesy widoczne: 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.
    • Procesy niewidoczne: 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, które wykluczają 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 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 pamięci globalnej.

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 raportuje stan operacyjny i aktywne dane ogranicznika pamięci:

adb shell am memory-limiter status

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 bezwzględne limity pamięci (memory.high) obecnie egzekwowane dla każdego stanu widoczności.
visibleSwap i notVisibleSwap
Obliczone bezwzględne limity 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 ograniczenia 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 wskazujący MiB lub sufiks GB wskazujący 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 czas życia konkretnej instancji procesu. Jeśli proces zostanie ponownie uruchomiony, wróci do standardowych limitów na podstawie swojego stanu.

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