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 zapobiega zużywaniu przez poszczególne aplikacje nieproporcjonalnie dużej ilości pamięci systemowej, co zmniejsza ogólne obciążenie pamięci i zapobiega agresywnemu zamykaniu procesów krytycznych 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 Service (AMS), aby śledzić zdarzenia cyklu życia procesu i zmiany stanu. Ogranicznik pamięci wymusza limity pamięci przy użyciu hierarchii cgroup v2 jądra systemu Linux.

Aby obsługiwać ogranicznik pamięci, jądro urządzenia musi mieć włączoną grupę kontrolną w wersji 2 i kontroler memory. Usługa korzysta z tych atrybutów:

memory.high
Limit. Po przekroczeniu tego limitu proces jest ograniczany, a jądro próbuje proaktywnie odzyskać stronę z tego procesu.
memory.swap.max
Ogranicza maksymalną ilość przestrzeni wymiany (np. ZRAM), którą może wykorzystać proces.

Wpływ na aplikacje

Aplikacje, które działają w ramach limitów pamięci, nie są objęte działaniem ogranicznika pamięci.

Gdy aplikacja przekroczy limit memory.high, jądro usuwa jej czystą pamięć opartą na plikach i zamienia nieaktywną pamięć anonimową, aby utrzymać aplikację w granicach limitu. W wyniku tego wykluczenia strony i wymiany aplikacja może doświadczyć tymczasowego ograniczenia wykonywania, ale nadal będzie działać.

W skrajnych sytuacjach, takich jak niekontrolowany wyciek pamięci, gdy aplikacja nadal przydziela pamięć anonimową po wyczerpaniu pamięci 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 systemu podstawowego są wyłączone, aby zachować niezbędną 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. aktywność, na której użytkownik jest obecnie skupiony, lub aktywną interakcję). Wyświetlanie interfejsu wymaga większego zestawu roboczego na potrzeby renderowania i potoków graficznych, dlatego widoczne procesy mają większy limit pamięci.

  • Niewidoczne procesy: procesy wykonujące pracę w tle bez wyświetlania interfejsu (np. usługi w tle lub odbiorniki komunikatów). Ponieważ nie renderują komponentów interfejsu, obowiązuje je bardziej restrykcyjny limit.

W tej tabeli znajdziesz mapowanie konkretnych stanów procesów 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 zapisanym 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 zarejestrowanie anomalii w statsd.

Konfiguracja

W Androidzie 17 i nowszych ogranicznik pamięci jest konfigurowany za pomocą standardowego pliku konfiguracji platformy na partycji systemowej w /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.

Zrozumienie wydzielonych obszarów i granic pamięci: wartość MemTotal jądra odzwierciedla fizyczną pamięć DRAM dostępną dla systemu operacyjnego po odjęciu wydzielonych obszarów pamięci sprzętu i oprogramowania układowego. Wydzielone obszary rezerwują pamięć RAM dla specjalistycznych komponentów sprzętowych, takich jak GPU, modem pasma podstawowego, procesor sygnałowy aparatu, bezpieczne środowiska wykonawcze i hipernadzorcy, które zwykle zużywają od 500 MiB do ponad 2 GiB pamięci fizycznej.

Ponieważ wartość MemTotal jest niższa niż deklarowana fizyczna pamięć DRAM urządzenia, progi minimumRequiredMemTotal uwzględniają margines na te wyłączenia. Na przykład konfiguracja kierowana na urządzenia z 16 GB pamięci określa dolny próg na poziomie 14336 (14 GiB), a nie 16384 (16 GiB), a poziom dla urządzeń z 12 GB pamięci określa dolny próg na poziomie 9216 (9 GiB). Dzięki temu każde urządzenie będzie odpowiadać zamierzonemu poziomowi niezależnie od różnic w wycięciach na poziomie płyty.

maximumAllowedMemTotal

Maksymalna ilość pamięci systemowej MemTotal (w MiB, bez górnego limitu), dla której ten limit ma zastosowanie. Jeśli wartość MemTotal jest większa niż ten pułap lub mu równa, ustawiony limit nie ma zastosowania. W przypadku urządzeń z pamięcią 18 432 MiB lub większą albo mniejszą niż 3200 MiB ogranicznik pamięci nie stosuje limitów, pozostawiając procesy bez ograniczeń.

memVisible

Miękki limit pamięci (memory.high) w MiB stosowany do widocznych procesów.

memNotVisible

Miękki limit pamięci (memory.high) w MiB stosowany do niewidocznych procesów.

swapVisible

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

swapNotVisible

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

Standardowe limity platformy

W Androidzie 17 i nowszych platforma określa standardowe limity pamięci i pamięci wymiany w /system/etc/memory-limiter-config.xml na różnych poziomach fizycznej pamięci RAM za pomocą przedziałów zakresu [floor, ceiling):

Docelowa fizyczna pamięć RAM Zakres MemTotal [dolny, górny)
(minimumRequiredMemTotal,
maximumAllowedMemTotal)
Limit widocznej pamięci
(memVisible)
Limit pamięci niewidocznej
(memNotVisible)
Limit widocznych zamian
(swapVisible)
Limit zamian niewidocznych
(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 limitu pamięci

Konfiguracja ogranicznika pamięci została zaprojektowana zgodnie z tymi zasadami 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 przepustowości lub przedwczesnego zakończenia działania.

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

    • Widoczne procesy: przydzielono około 1/2 do 2/3 całkowitej fizycznej pamięci RAM na potrzeby obsługi aktywnego interfejsu, kompozycji i renderowania.
    • Niewidoczne procesy: na zadania w tle przydzielane jest około 1/4 do 1/3 całkowitej pamięci RAM.
  • Uniwersalne zastosowanie: limity obowiązują jednolicie we wszystkich procesach aplikacji na urządzeniu (UID >= 10000), w tym we wstępnie zainstalowanych aplikacjach systemowych i OEM. Ogranicznik pamięci nie obsługuje list dozwolonych, które umożliwiają wykluczenie określonych aplikacji, co zapewnia sprawiedliwe zarządzanie pamięcią we wszystkich aplikacjach. Aby skonfigurować samoczynnie dostosowujące się progi ochrony memory.min w przypadku krytycznych interaktywnych demonów oraz bardziej rygorystyczne, dostosowane do obciążenia limity memory.high w przypadku poszczególnych aplikacji i usług systemowych, zapoznaj się z artykułem Budżety pamięci systemowej.

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

  • Dynamika odzyskiwania i wymiany pamięci przez jądro: gdy proces aplikacji zbliża się do limitu memory.high, jądro Linuksa kieruje odzyskiwanie pamięci konkretnie do grupy kontrolnej tego procesu. Obejmuje to usuwanie nieaktywnych stron opartych na plikach i przenoszenie nieużywanych stron anonimowych do ZRAM, co pozwala zachować płynność działania 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 w celu programowania, testowania i debugowania:

am memory-limiter <SUB-COMMAND>

status

Podpolecenie status raportuje stan działania i aktywne wskaźniki 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

Najważniejsze pola w wynikach:

monitoring
Wskazuje, czy ogranicznik pamięci aktywnie monitoruje procesy.
visibleMem i notVisibleMem
Obliczone bezwzględne limity pamięci (memory.high) obecnie stosowane w przypadku każdego stanu widoczności.
visibleSwap i notVisibleSwap
Obliczone bezwzględne limity zamiany (memory.swap.max) obecnie stosowane w przypadku każdego stanu widoczności.
events
Liczba przypadków, w których proces przekroczył przypisany limit.
processes
Bieżąca liczba monitorowanych procesów.

ignoruj

Podpolecenie ignore tymczasowo wyklucza określony identyfikator UID lub wszystkie procesy z ograniczania pamięci. Jest to przydatne do testów porównawczych wydajności, testów 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 konkretnego procesu według identyfikatora procesu (PID) niestandardową wartością bezwzględną w bajtach. Wartość musi być liczbą całkowitą, ale może zawierać sufiks MB oznaczający MiB lub sufiks GB oznaczają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 działania danej instancji procesu. Jeśli proces zostanie uruchomiony ponownie, powróci do standardowych limitów na podstawie swojego stanu.

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