W Androidzie 17 i nowszych platforma zarządza pamięcią procesów systemowych za pomocą 2 uzupełniających się mechanizmów cgroup v2 systemu Linux, które działają razem z ogólnym limitem pamięci aplikacji:
- Dolne limity ochrony demonów systemowych (
SystemMemoryBudgetManagerzmemory.min): samoregulujące się rezerwacje cgroup v2memory.min(ustawione na 90% ustalonego zestawu roboczego każdego demona i ograniczone globalnie do 10% całkowitej fizycznej pamięci RAM) chronią krytyczne demony interaktywne i procesy powłoki (system_server,surfaceflinger, graficzny HALcomposer,audioserver,cameraserver,netd,SystemUIi program uruchamiający Home) w przypadku obciążenia pamięci, zastępując statyczne przypinanie plikówPinnerServicemlock. - Limity pamięci aplikacji systemowych (
<memory-budget>zmemory.high): skalibrowane pod kątem obciążenia limity miękkie cgroup v2memory.high(zwykle od 30 MB do 190 MB) zadeklarowane w plikach manifestu dla trwałych i działających w tle aplikacji systemowych (Bluetooth,Nfc,CalendarProvider,Traceur,MediaProvider,:PhotoPicker,NetworkStack,AppSearchiTeleService) proaktywnie odzyskują pamięć podręczną stron plików nieaktywnych i anonimową stertę po gwałtownym wzroście obciążenia, zanim nastąpi globalne obciążenie pamięci.
W tabeli poniżej porównano oba mechanizmy:
| Wymiar | Minimalne wartości ochrony demonów systemowych | Limity pamięci aplikacji systemowych |
|---|---|---|
| Usługa platformowa | SystemMemoryBudgetManager |
MemoryLimiter |
| Kontrola cgroup v2 | memory.min – twarda minimalna cena docelowa |
memory.high – pułap miękkiego odzyskiwania |
| Procesy docelowe | Interaktywne procesy i interfejs powłoki, w przypadku których czas oczekiwania ma kluczowe znaczenie (system_server, surfaceflinger, composer HAL, audioserver, cameraserver, netd, SystemUI i aplikacja Home) |
trwałe i bezgłowe aplikacje systemowe, mechanizmy dostarczania treści i usługi w tle (takie jak Bluetooth, Nfc, MediaProvider, NetworkStack, AppSearch i TeleService); |
| Model doboru rozmiaru | samodzielne dostrajanie w czasie działania (90% ustalonego zestawu roboczego, globalnie ograniczone do 10% całkowitej pamięci RAM) bez tabel dostrajania na urządzenie; | Atrybuty XML deklaratywnego pliku manifestu <memory-budget> skalibrowane według poziomu pamięci RAM, stanu procesu i liczby pikseli wyświetlacza |
| Główna korzyść | Zapobiega zacinaniu się interaktywnego interfejsu i blokowaniu wybudzania przy dużym obciążeniu pamięci podczas zastępowania statycznych list plików PinnerService mlock |
Stale usuwa nieużywaną pamięć podręczną jednorazowych plików i stertę po gwałtownym wzroście, aby pozostawić więcej fizycznej pamięci RAM dla aplikacji na pierwszym planie i aplikacji w pamięci podręcznej |
Więcej informacji o składni <memory-budget>, atrybutach XML, interfejsach API środowiska wykonawczego i rozliczaniu pamięci systemu operacyjnego znajdziesz w artykułach Ustawianie budżetów pamięci aplikacji i Omówienie i rozwiązywanie problemów z pamięcią Androida.
Ochrona krytycznych interaktywnych demonów za pomocą SystemMemoryBudgetManager
SystemMemoryBudgetManager utrzymuje procesy, które znajdują się bezpośrednio na ścieżce interaktywnej użytkownika, w fizycznej pamięci RAM podczas obciążenia pamięci. Zamiast przypinać statyczną listę plików APK, ODEX i VDEX do pamięci za pomocą mlock (jak robi to PinnerService w przypadku config_defaultPinnerServiceFiles i config_pinnerHomePinBytes), SystemMemoryBudgetManager przypisuje dynamiczne wartości minimalne cgroup v2 memory.min do tych procesów:
- Wycinek systemu podstawowego (
/sys/fs/cgroup/system):system_server,surfaceflinger, HAL grafikicomposer,audioserver,cameraserverinetd, a także wszelkie natywne demony systemowe lub demony dostawcy specyficzne dla formatu wymienione wconfig_systemMemoryBudgetAdditionalSystemProcesses(np.android.hardware.perceptionhostiauracamerahalna urządzeniach z XR). - Interaktywny wycinek aplikacji (
/sys/fs/cgroup/apps): główny proces skonfigurowanego pakietu SystemUI (config_systemUi) i główny proces bieżącego użytkownika z rolą ekranu głównego.
Ochrona posiadacza roli Home i config_systemUi zapewnia ochronę na wszystkich rodzajach urządzeń. Na urządzeniach, na których config_systemUi jest lekkim pakietem zgodności (np. Wear OS, gdzie tarcza zegarka, powiadomienia i kafelki działają w aplikacji Home), powłoka dla użytkownika nadal jest w pełni chroniona. Jeśli intencja Home zostanie rozwiązana do pakietu config_systemUi, proces jest chroniony raz. Tylko główny proces każdego pakietu (processName == packageName) otrzymuje minimalny poziom ochrony, więc procesy pomocnicze (takie jak :screenshot) nie zużywają budżetu ochrony.
Zasada samodzielnego dostrajania i 3-poziomowe przekazywanie uprawnień cgroup
Zestaw roboczy interaktywnego demona różni się znacznie w zależności od formatu i pojemności pamięci RAM (np. SystemUI wykorzystuje około 45 MB na zegarku, 60 MB na telefonie z 6 GB pamięci RAM i 180 MB na telefonie z 16 GB pamięci RAM), dlatego statyczne tabele przeglądowe dla poszczególnych urządzeń są niestabilne. Zamiast tego SystemMemoryBudgetManager określa wartości minimalne bezpośrednio na podstawie pamięci rezydentnej każdego procesu, używając 3 stałych:
| Parametr zasady | Wartość domyślna | Zachowanie |
|---|---|---|
| Cena minimalna na proces | 90% rozliczonych memory.current minus niezmapowana pamięć podręczna plików |
Chroni anonimową stertę, slab i gorący zmapowany kod, pozostawiając najzimniejsze 10% ogona do odzyskania, dzięki czemu naprawdę zimne strony nadal można usunąć. Niezmapowana pamięć podręczna plików (max(0, file - shmem - file_mapped) z memory.stat) jest odejmowana przed zastosowaniem współczynnika 90%, aby pamięć podręczna skanowania pakietów rozruchowych lub jednorazowe odczyty zasobów nie zawyżały wartości progowych. |
| Globalny limit pamięci RAM | 10% całkowitej pamięci RAM (config_systemMemoryBudgetLowKb) |
Pasuje do zabezpieczenia PinnerService config_pinnerMaxPinnedMemoryPercentage. Jeśli suma 90% cen minimalnych przekracza 10% wartości MemTotal, każda cena minimalna jest skalowana w dół proporcjonalnie o ten sam współczynnik. |
| Częstotliwość odświeżania | 1, 4, 16 i 64 s po uruchomieniu, a następnie co 30 min | Geometryczna seria stabilizacji zbiega się z zestawem roboczym każdego demona po uruchomieniu bez kodowania na stałe opóźnień rozgrzewania na urządzenie, a 30-minutowe odświeżanie stanu stabilnego śledzi stopniowy wzrost i zmniejszanie się zestawu roboczego w ciągu okresu eksploatacji urządzenia. |
W kontrolerze pamięci cgroup v2 w systemie Linux (mm/memcontrol.c:effective_protection) efektywna ochrona memory.min grupy cgroup liścia jest ograniczona przez memory.min skonfigurowane w jej grupach cgroup nadrzędnych i przodkach. Ustawienie memory.min tylko w katalogu liścia pid_<pid>, gdy uid_<uid>/memory.min ma wartość 0, nie zapewnia żadnej ochrony.
Z kolei ustawienie stałej wartości memory.min tylko w górnym wycinku /sys/fs/cgroup/system powoduje rozłożenie nieprzypisanej ochrony na wszystkie podrzędne identyfikatory UID i procesy proporcjonalnie (memory_recursiveprot), co pozwala procesom diagnostycznym lub pomocniczym współdzielącym uid_1000 (android.uid.system) na przejęcie ochrony z surfaceflinger i audioserver.
Aby zapewnić dokładną izolację poszczególnych demonów, SystemMemoryBudgetManager zapisuje ochronę na wszystkich 3 poziomach hierarchii cgroup v2 przy każdym odświeżeniu, jak pokazano na rysunku 1:
- Węzeł procesu liścia (
pid_<pid>/memory.min): ustawiony na (skalowany) 90-procentowy dolny limit zestawu roboczego procesu. - Węzeł UID (
uid_<uid>/memory.min): ustawiony na dokładną sumę zarządzanych przez niegopid_*poziomów liści, dzięki czemu niezarządzane procesy współdzielące ten UID (takie jak inneAID_SYSTEMdemony lub pakietysharedUserId="android.uid.system") mająmemory.min = 0i nie mogą żądać ochrony. - Wycinek najwyższego poziomu (
/sys/fs/cgroup/system/memory.mini/sys/fs/cgroup/apps/memory.min): ustawiony na dokładną sumęuid_*wycinków podrzędnych. Wszystkie nieaktualne wartościmemory.loww zarządzanych węzłach są czyszczone do wartości0, a wszystkie katalogiuid_*, które były zarządzane podczas poprzedniego odświeżania (np. poprzedni program uruchamiający ekranu głównego po przełączeniu przez użytkownika), są resetowane do wartości0.
Rysunek 1. Trzypoziomowa ochrona delegowania poziomów w ramach wycinków cgroup.
Dlaczego w jądrach MGLRU używana jest wartość memory.min zamiast memory.low
W przypadku wielogeneracyjnego LRU (MGLRU, domyślnego mechanizmu odzyskiwania stron w jądrach Androida 6.6 i nowszych) memory.low i memory.min zachowują się inaczej w mm/vmscan.c:shrink_one:
memory.low(odroczenie jednoprzebiegowe): grupa kontrolna, której wykorzystanie jest poniżejmemory.low, jest przenoszona na koniec listy LRU memcg (MEMCG_LRU_TAIL) i pomijana w jednym przebiegu odzyskiwania. Przy następnym przejściu w warunkach utrzymującego się obciążenia pamięci jądro odzyskuje całą grupę kontrolną i rejestruje zdarzenieMEMCG_LOW. W testach obciążeniowych urządzeń fizycznych z anonimowym procesem zużywającym pamięć, który rozrastał się do 85% fizycznej pamięci RAM,memory.lowpozwolił na zmniejszenie zużycia pamięci przezSystemUIna zegarku z ok. 45 MB do ok. 5–10 MB, a w przypadkuaudioserverna składanym urządzeniu z 16 GB pamięci – z 15 MB do 2 MB przy minimalnym zużyciu na poziomie ok. 14 MB.memory.min(sztywny dolny limit ochrony): grupa kontrolna, której wykorzystanie jest poniżejmemory.min, jest pomijana przez globalne odzyskiwanie. Jak pokazano na rysunku 2, przy tym samym obciążeniu pamięci RAM na poziomie 85%memory.minodrzuca tylko najmniej używane 10% każdego demona i utrzymujeSystemUI,surfaceflingeriaudioserverna poziomie 90% bez ponownych błędów anonimowych i bez wzrostu liczby zakończeń procesówlmkd.
memory.min zapewnia twardą rezerwację fizyczną odpowiadającą ochronie przypinania PinnerService mlock, które zastępuje, z tą zaletą, że LRU jądra (a nie statyczna lista plików) wybiera, które strony kodu, sterty i grafiki pozostają rezydentne.
Rysunek 2. Utrzymywanie zestawu roboczego demona przy rosnącym obciążeniu pamięci.
Wstępne rozgrzewanie podczas uruchamiania i odzyskiwanie pamięci podręcznej niezmapowanych plików po uruchomieniu
Zastąpienie stałego PinnerService mlock config_defaultPinnerServiceFiles wymaga skoordynowania 2 zachowań podczas uruchamiania:
- Jednorazowe wstępne rozgrzewanie podczas uruchamiania w tle (
PinnerService): pomijaniePinnerServicemlockbez rozgrzewania pamięci podręcznej strony powoduje przesunięcie odczytów dysku APK, ODEX i VDEX o rozmiarze ok. 95 MB do ścieżki synchronicznego uruchamiania, gdy aplikacjaSystemUIsię uruchamia (OnBootPhase_550), co wydłuża czas uruchamiania o 300–450 ms.SystemUIWywołaniemadvise(MADV_WILLNEED)nie wystarczy, ponieważforce_page_cache_raw jądrze ogranicza pojedyncze wywołanieMADV_WILLNEEDdo jednego okna odczytu z wyprzedzeniem (zwykle od 128 KB do 2 MB). Zamiast tego, gdySystemMemoryBudgetManager.shouldSkipDefaultSystemPinningjest aktywny,PinnerServicemapuje każdy plik wconfig_defaultPinnerServiceFilesw wątku w tle podczasonStart, synchronicznie wypełnia plik w pamięci podręcznej strony za pomocąOs.mlocki natychmiast wywołujeOs.munlock,munmapiclose. Pozwala to zachować czas szybkiego uruchamiania, nie pozostawiając przy tym żadnych trwale zablokowanych bajtów, dzięki czemu strony uruchamiane „na zimno” można później usunąć. - Wykluczanie i odzyskiwanie pamięci podręcznej skanowania pakietów rozruchowych
system_server: podczas uruchamianiaPackageManagerServiceskanuje zainstalowane pakiety APK, pozostawiając od 1,5 do 2,5 GB nieodwzorowanej pamięci podręcznej stron plików, która jest przypisana do grupy kontrolnejsystem_server. Jeśli uwzględnimy to w obliczeniachmemory.mindlasystem_server, pamięć podręczna zimnego rozruchu nasyci 10% globalnego limitu pamięci RAM i proporcjonalnie zmniejszy dolne limity ochrony dlasurfaceflinger,SystemUIiaudioserver. Aby zapobiec niedoborowi limitu,SystemMemoryBudgetManagerodczytujememory.statdlasystem_server, oblicza niezmapowaną pamięć podręczną plików jakomax(0, file - shmem - file_mapped)i odejmuje tę niezmapowaną pamięć podręczną odmemory.currentprzed obliczeniem dolnego limitu 90%, jak pokazano na rysunku 3. Dodatkowo 10 sekund poPHASE_BOOT_COMPLETEDmenedżer wykonuje jednorazowy zapis<unmapped_file> swappiness=0w węźlememory.reclaimelementusystem_serverw systemie Linux 6.2 i nowszych jądra, aby proaktywnie usunąć nieużywaną pamięć podręczną skanowania pakietów bez zamiany anonimowej sterty lub usunięcia zmapowanych stron DEX i stron kodu. W przypadku starszych jąder (takich jakandroid14-6.1), które odrzucająswappiness=0z błędemEINVAL, menedżer pomija zapismemory.reclaim, zamiast powracać do niekwalifikowanego zapisu odzyskiwania, który podczas uruchamiania zamieniłby anonimową stertę.
Rysunek 3. Konwergencja minimalnego poziomu ochrony i odzyskiwanie pamięci podręcznej po uruchomieniu.
Wymagania dotyczące jądra i nakładki konfiguracji urządzenia
SystemMemoryBudgetManager korzysta z mem_cgroup_calculate_protection w jądrze Linuksa, aby propagować memory.min w zagnieżdżonych grupach kontrolnych. Wspólne jądra Androida android15-6.6 i android16-6.12 od razu zapewniają pełną obsługę ochrony zagnieżdżonej MGLRU. Urządzenia z systemem android14-6.1 i włączoną funkcją MGLRU wymagają 3 backportów upstream w mm/vmscan.c i mm/memcontrol.c, aby puste pośrednie grupy kontrolne slice i UID propagowały ochronę do grup kontrolnych procesów liści:
30d77b7eef01(mm/mglru: fix ineffective protection calculation)8aa420617918(mm/mglru: respect min_ttl_ms with memcgs)adb8213014b2(mm: memcg: fix stale protection of reclaim target memcg)
Producenci OEM mogą dostosować lub wyłączyć SystemMemoryBudgetManager za pomocą tych nakładek zasobów platformy:
config_systemMemoryBudgetLowKb(liczba całkowita, KB):0(domyślnie) ogranicza sumę wszystkich wartościmemory.mindo 10% całkowitej pamięci RAM. Wartość dodatnia ustawia wyraźny limit w KB, a wartość ujemna wyłączaSystemMemoryBudgetManagerna urządzeniu (zachowując starsze statyczne przypinaniemlockPinnerService).config_systemMemoryBudgetHighKb(liczba całkowita, KB):0(domyślnie) pozostawia/sys/fs/cgroup/system/memory.highna poziomiemax. Wartość dodatnia ustawia pułap ceny minimalnej dla najwyższego wycinkamemory.high(musi być większa lub równa pułapowi ceny minimalnej).config_systemMemoryBudgetAdditionalSystemProcesses(tablica tekstowa): zawiera listę dodatkowych nazw procesów demonów dostawcy lub systemu natywnego o krytycznym znaczeniu dla opóźnienia w/sys/fs/cgroup/system(dopasowanych do/proc/<pid>/commlub nazwy podstawowejargv[0]w/proc/<pid>/cmdlinedla identyfikatorów UID systemu i dostawcy w10000), które mają być chronione przez 90%memory.minwraz z 6 domyślnymi demonami systemowymi. Użyj tej nakładki zamiast ustawiaćmemory.minwinit.rc, ponieważSystemMemoryBudgetManagerponownie obliczauid_<uid>/memory.minjako sumę zarządzanych procesów liści i czyści niezarządzane węzłyuid_*przy każdym odświeżeniu. Na przykład urządzenie z XR może chronić demony HAL śledzenia przestrzennego i aparatu za pomocą tej nakładki:<string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses"> <item>android.hardware.perceptionhost</item> <item>auracamerahal</item> </string-array>config_systemUi(ciąg tekstowy): nazwa pakietu aplikacji SystemUI platformy, rozwiązana za pomocąMATCH_SYSTEM_ONLY.
Aby sprawdzić aktywne piętra SystemMemoryBudgetManager i węzły cgroup na urządzeniu, uruchom:
# Inspect policy state, resolved PIDs, and applied floors in dumpsys meminfo adb shell dumpsys meminfo | sed -n '/System Memory Budgets/,$p'# Inspect raw cgroup v2 memory.min nodes across the system and apps slices adb shell head -n 1 \ /sys/fs/cgroup/system/memory.min \ /sys/fs/cgroup/system/uid_*/memory.min \ /sys/fs/cgroup/system/uid_*/pid_*/memory.min \ /sys/fs/cgroup/apps/memory.min \ /sys/fs/cgroup/apps/uid_*/memory.min \ /sys/fs/cgroup/apps/uid_*/pid_*/memory.min
Powiązanie trwałych i działających w tle aplikacji systemowych z budżetami pamięci
SystemMemoryBudgetManager chroni interaktywne demony przed odzyskaniem za pomocą poziomów memory.min, ale wiele innych aplikacji systemowych i uprzywilejowanych usług platformy działa w tle przez długi czas jako procesy PERSISTENT, powiązane usługi na pierwszym planie lub często odpytywane mechanizmy dostarczania treści.
Bez wyraźnych limitów pamięci procesy te nie są ograniczane, dopóki nie osiągną limitów ogranicznika pamięci wynoszących kilka gigabajtów (np. od 4 GB do 10 GB w przypadku widocznych aplikacji i od 2 GB do 5 GB w przypadku aplikacji działających w tle) lub dopóki globalne obciążenie pamięci nie wywoła działania kswapd i procesu zabijania procesów przy małej ilości pamięci (lmkd) w całym systemie.
Jeśli procesy systemowe w tle nie są ograniczone, często gromadzą duże ilości pamięci nieaktywnej, która nie jest częścią ich aktywnego zestawu roboczego:
- Nieograniczona pamięć podręczna stron plików: usługi, które indeksują multimedia, przeszukują bazy danych lub synchronizują tabele kalendarza i dostawcy (takie jak
AppSearch,MediaProvideriCalendarProvider), mapują lub przesyłają strumieniowo duże pliki na dysku. Bez limitumemory.highgrupy kontrolnej czyste strony z plikami z jednorazowych odczytów pozostają obciążone grupą kontrolną procesu na czas nieokreślony, zużywając setki megabajtów fizycznej pamięci RAM. - Przechowywanie anonimowej sterty po gwałtownym wzroście aktywności: usługi, w których występują przejściowe skoki aktywności, takie jak
Bluetoothgwałtowne wzrosty aktywności związane z wykrywaniem dźwięku i BLE,NetworkStackweryfikacja TLS portalu przechwytującego lub przechodzenie pomocników interfejsu w tło, mogą przechowywać nieaktywne strony anonimowej sterty długo po zakończeniu gwałtownego wzrostu aktywności.
Zastosowanie skalibrowanych deklaracji <memory-budget> (memory.high limity miękkie, zwykle od 30 MB do 190 MB) do aplikacji systemowych zapewnia 2 główne korzyści:
- Proaktywne, zlokalizowane odzyskiwanie stron: gdy proces systemowy przekroczy skonfigurowany budżet
memory.high, jądro Linuksa kieruje odzyskiwanie stron konkretnie do tej grupy kontrolnej, najpierw usuwając nieużywane strony powiązane z plikami i kompresując nieaktywne strony anonimowe do ZRAM bez czekania na globalne obciążenie pamięci urządzenia. - Więcej dostępnej pamięci RAM dla aplikacji na pierwszym planie i puli aplikacji w pamięci podręcznej: ograniczenie pamięci podręcznej nieużywanych plików w usługach systemowych, które są zawsze włączone, pozwala odzyskać od kilkudziesięciu do kilkuset megabajtów fizycznej pamięci RAM na urządzeniu, co zmniejsza zacinanie się interfejsu, obniża obciążenie podczas uruchamiania aparatu i utrzymuje więcej aplikacji użytkownika w stanie zapisanym w pamięci podręcznej.
Konfigurowanie budżetów pamięci aplikacji systemowych
Aplikacje systemowe używają tej samej składni pliku manifestu <memory-budget>, kaskadowego stanu procesu, skalowania gęstości wyświetlania i reguły rozwiązywania ostatniej pasującej klauzuli co aplikacje ogólne. Pełną listę atrybutów znajdziesz w dokumentacji atrybutów XML.
Producenci OEM mogą deklarować elementy <memory-budget> bezpośrednio w pliku AndroidManifest.xml aplikacji systemowej lub scalać je z osobnego pliku XML za pomocą właściwości additional_manifests w pliku Android.bp. Zgodnie z konwencją AOSP przechowuje referencyjne budżety aplikacji systemowych w samodzielnych plikach *-budget.xml (w frameworks/base/core/memory_budgets/ w przypadku pakietów platformy podstawowej lub obok poszczególnych modułów, takich jak packages/modules/NetworkStack/memory_budgets/, packages/modules/AppSearch/apk/ i packages/services/Telephony/) i dołącza je za pomocą additional_manifests:
android_app {
name: "TeleService",
manifest: "AndroidManifest.xml",
additional_manifests: ["Telephony-budget.xml"],
// ...
}
Zakres na poziomie pakietu (UID) i na poziomie procesu (PID)
To, czy zadeklarujesz podstawowy budżet na poziomie pakietu (<application>) czy budżet wieloprocesowy (<processes><process>), określa, czy jądro wymusza memory.high na poziomie grupy kontrolnej UID (/sys/fs/cgroup/uid_<uid>/memory.high) czy na poziomie grupy kontrolnej PID (/sys/fs/cgroup/uid_<uid>/pid_<pid>/memory.high), jak pokazano na rysunku 4:
- Budżet na poziomie pakietu (
<application>): używaj, gdy pakiet APK uruchamia pojedynczy proces w ramach własnego dedykowanego identyfikatora UID (np.Bluetooth,Nfc,NetworkStack,CalendarProvider,TraceurlubAppSearch). - Limit na poziomie procesu (
<processes><process>): używaj, gdy plik APK udostępniaandroid:sharedUserId(np.android.uid.phonez identyfikatorem UID1001lubandroid.uid.systemz identyfikatorem UID1000) innym pakietom systemowym, aby inne procesy udostępniające ten identyfikator UID nie były ograniczane razem, lub gdy pojedynczy plik APK uruchamia wiele procesów z różnymi rolami pamięci (np. procesMediaProviderdziałający w tle i podproces interfejsu:PhotoPicker).
Rysunek 4. Limity pamięci na poziomie UID i PID.
Poniższy przykład pokazuje MediaProvider-budget.xml, który ogranicza budżety na poziomie pamięci RAM do domyślnego procesu MediaProvider (<process>) i budżetu skalowanego do wyświetlacza do procesu interfejsu :PhotoPicker. Każdy atrybut android:maxDeviceRamMb odpowiada urządzeniom, których łączna fizyczna pamięć RAM (MemTotal) jest równa lub mniejsza od określonego progu w MB (lub dotyczy dowolnej pojemności pamięci RAM, jeśli zostanie pominięty). Klauzule są uporządkowane od domyślnej bez ograniczeń do najmniejszego progu maxDeviceRamMb, dzięki czemu reguła rozstrzygania ostatniej pasującej klauzuli wybiera najbardziej rygorystyczny poziom:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.providers.media.module">
<uses-sdk android:targetSdkVersion="10000" />
<application>
<processes>
<process>
<memory-budget
android:maxMb="85" />
<memory-budget
android:maxMb="75"
android:maxDeviceRamMb="12288" />
<memory-budget
android:maxMb="60"
android:maxDeviceRamMb="8192" />
</process>
<process android:process=":PhotoPicker">
<memory-budget
android:maxMb="85"
android:additionalBytesPerDisplayPixel="25" />
</process>
</processes>
</application>
</manifest>
Jak skalibrowano referencyjne budżety AOSP na urządzeniach Google Pixel
Limity pamięci aplikacji systemowych w AOSP zostały skalibrowane na urządzeniach Google Pixel z pamięcią RAM od 6 GB do 16 GB (w tym telefony, urządzenia składane i tablety z 6 GB, 8 GB, 12 GB i 16 GB pamięci RAM) oraz rozdzielczością wyświetlacza od ok.1, 2 MP (720 × 1600) do ok.4, 5 MP (2076 × 2152).
Kalibracja łączyła 2 uzupełniające się źródła danych: dane telemetryczne z pól cgroup v2 z całej floty oraz kontrolowane skanowanie memory.high na urządzeniach fizycznych w ramach głównych ścieżek użytkownika.
Analiza danych telemetrycznych cgroup v2 w całej flocie
Okresowe migawki cgroup v2 były zbierane na wszystkich urządzeniach i dzielone według poziomu fizycznej pamięci RAM (≤ 8 GB, 12 GB i ≥ 16 GB) oraz zasobnika stanu procesu (foreground, perceptible, background i cached). Analiza statystyczna opierała się na 6 zasadach:
- Rozkład
memory.currentna zestaw roboczy i pamięć podręczną plików, którą można odzyskać: ponieważmemory.highzarządza obciążeniem grupy kontrolnej (memory.current), każdy zrzut został rozłożony za pomocąmemory.statna anonimową stertę (anon), zamienione anonimowe strony (swap) i pamięć podręczną stron opartą na plikach (file), jak pokazano na rysunku 5. Łączne nieskompresowane anonimowe zapotrzebowanie (anon + swapprzy P99) określa minimalny poziom zbioru roboczego dla budżetu, dzięki czemu proces nie jest narażony na wyczerpanie pamięci ZRAM, a duża różnica międzymemory.currentP99 aanon + swapP99 wskazuje na pamięć podręczną stron plików, którąmemory.highmoże odzyskać, pod warunkiem że pamięć podręczna jest niezmapowana i nieaktywna. - Uwzględnij pamięć, której
memory.highnie może odzyskać: nie wszystkie pliki i pamięć współdzielona są zimną pamięcią podręczną. Strony zablokowane (unevictablewmemory.stat, np. pamięć współdzielona zablokowana za pomocąmlocklub zablokowana pamięć RAM gościa maszyny wirtualnej, której procesy hosta są obciążane w grupie kontrolnej aplikacji) ustawiają twardy limit budżetu, a zmapowane strony (file_mapped) często są częścią aktywnego zestawu roboczego. Zanim uznasz, że pamięć pliku można odzyskać, sprawdźunevictable,shmemifile_mapped. Na przykład około 100 MB pamięciAppSearchjest zablokowane i nie można go usunąć, więc jego budżet obejmuje tę wartość minimalną oraz zestaw roboczy hosta, z miejscem na rozszerzenie zablokowanego regionu. - Odfiltrowanie rozcieńczenia w stanie bezczynności: dostawcy treści (
CalendarProvider) i narzędzia dla programistów na żądanie (Traceur) spędzają od 70% do 99,9% czasu w stanie bezczynności, zajmując od 1 MB do 3 MB pamięci. Obliczanie nieprzetworzonych percentyli we wszystkich zrzutach znacznie obniża szacunki P95 i P99. Zamiast tego percentyle i przedziały ufności zostały obliczone na podstawie nierozcieńczonych aktywnych zrzutów (po odfiltrowaniu zrzutówCACHEDi zrzutów bazowych stanu bezczynności). - Używaj przedziałów ufności pogrupowanych według urządzeń: ponieważ jedno urządzenie dostarcza wiele okresowych zrzutów w czasie, traktowanie zrzutów jako niezależnych próbek zaniża niepewność ogona. Wszystkie szacunki dotyczące zestawu roboczego w 95 i 99 percentylu zostały ocenione przy użyciu konserwatywnych 95-procentowych przedziałów ufności dla klastrów urządzeń (przy użyciu liczby różnych urządzeń
N_devjako efektywnej wielkości próby). - Uwzględnianie przejściowych skoków za pomocą
memory.peak: próbkowanie okresowe może pomijać krótkotrwałe skoki o wysokiej alokacji (takie jak walidacja TLS portalu przechwytującegoNetworkStacki równoczesne sondy DNS lub inicjowanieBluetoothA2DP i BLE). W przypadku usług o zmiennym obciążeniu budżety zostały dostosowane tak, aby pokrywały od 90 do 95 percentyla maksymalnego wykorzystania pamięcimemory.peakjądra w okresie eksploatacji na różnych poziomach pamięci RAM. - Sprawdź, czy poziom pamięci RAM odzwierciedla pamięć RAM, a nie generację urządzenia: nowsze urządzenia mają zwykle więcej pamięci RAM i więcej funkcji, więc zestaw roboczy, który rośnie wraz z poziomem pamięci RAM, może w rzeczywistości rosnąć wraz z liczbą funkcji. Zanim dodasz poziom
maxDeviceRamMb, podziel dane według modelu urządzenia. Na przykładcom.android.phoneosiąga ten sam 99 centylanon + swap(~85–90 MB) na telefonach z 12 GB i 16 GB pamięci RAM tej samej generacji, podczas gdy starszy składany telefon z 12 GB pamięci RAM wykorzystuje 40 MB. Dlatego budżet wykorzystuje jedną wartość bez poziomu pamięci RAM, dzięki czemu urządzenie z mniejszą ilością pamięci RAM, które obsługuje nowsze funkcje telefoniczne, nie ma zbyt małego budżetu.
Rysunek 5. Zestaw roboczy a pamięć podręczna plików, które można odzyskać.
CUJ na urządzeniu fizycznym i skalowanie wyświetlacza
Aby zweryfikować budżety pochodzące z telemetrii i skalibrować budżety proporcjonalne do wyświetlania (takie jak :PhotoPicker) oraz budżety tła zależne od stanu (takie jak Traceur), na urządzeniach fizycznych z pamięcią RAM od 6 GB do 16 GB przeprowadzono testy memory.high:
- Znajdowanie punktu zerowego ponownego błędu: podczas wykonywania głównych ścieżek użytkownika od początku do końca (takich jak szybkie przewijanie siatki zdjęć, przeglądanie albumów, przesuwanie podglądu zdjęć i filmów w wysokiej rozdzielczości na pełnym ekranie oraz nagrywanie i zapisywanie rekordów śledzenia) wartość
memory.highbyła zmniejszana podczas pomiaru liczników cgroupmemory.stat(workingset_refault_anon,workingset_refault_file,pgscanipgsteal) orazmemory.events(high). Jak pokazano na rysunku 6, optymalny budżet znajduje się tuż nad punktem zerowym ponownego błędu. Punkt zerowego ponownego błędu to próg, przy którym jądro odzyskuje zimną jednorazową pamięć podręczną plików (pgsteal > 0) bez wywoływania aktywnego thrashingu zbioru roboczego (workingset_refault == 0). - Weryfikacja skalowania pikseli wyświetlacza w różnych rozdzielczościach: testy
:PhotoPickerw 4 fizycznych i emulowanych rozdzielczościach wyświetlacza od ok.1,2 Mpix (720 × 1600) do ok.4,5 Mpix (2076 × 2152, rozłożony wyświetlacz wewnętrzny) potwierdziły, że aktywny zestaw roboczy bitmap Glide i ViewPager2 skaluje się liniowo wraz z liczbą pikseli wyświetlacza. Wartość bazowa bez wyświetlania również różni się w zależności od urządzenia: przy prawie tej samej liczbie pikseli punkt zerowy wynosił około 140 MB na zewnętrznym wyświetlaczu składanego urządzenia z 16 GB pamięci, a na telefonie z 6 GB pamięci – od 110 do 125 MB. Wartość bazowa 85 MB plus 25 bajtów na piksel wyświetlacza (android:maxMb="85"iandroid:additionalBytesPerDisplayPixel="25") pozostaje powyżej zmierzonego punktu przegięcia na każdym testowanym wyświetlaczu (~110 MB na HD+, ~145 MB na FHD+ i ~190 MB na rozłożonych wyświetlaczach). - Weryfikowanie czyszczenia pamięci ART w tle: w cgroup v2 strony obrazu frameworka i
boot.artudostępniane przez Zygote pozostają przypisane do cgroup Zygote i są traktowane jako przestrzeń odporna podczas czyszczenia pamięci ART w trybie równoczesnego oznaczania i kompaktowania (CMC). Testy obciążeniowe potwierdziły, że gdy aplikacja interfejsu systemu przechodzi do stanubackground, zaostrzeniememory.high(na przykład do 30 MB naTraceur) wyraźnie ogranicza prywatną stertę i pamięć podręczną stron bez powodowania nadmiernego czyszczenia pamięci ani aktywności wymiany.
Rysunek 6. Znajdowanie punktu zerowego błędu podczas CUJ.
Podsumowanie budżetów aplikacji systemowych w systemie referencyjnym AOSP
Tabela poniżej zawiera podsumowanie skalibrowanych budżetów referencyjnych w AOSP na różnych poziomach pamięci RAM i w różnych stanach procesów:
| Pakiet lub proces systemowy | Zakres | ≤ 8 GB RAM | 12 GB RAM | ≥ 16 GB RAM | Główny sterownik kalibracji |
|---|---|---|---|---|---|
com.android.bluetooth |
UID (<application>) |
80 MB | 95 MB | 110 MB | Obejmuje skoki memory.peak w ciągu cyklu życia P90 (ok. 75–110 MB) i ponad 2-krotność aktywnych memory.current P99. |
com.android.nfc |
UID (<application>) |
50 MB | 60 MB | 60 MB | Obejmuje aktywną emulację karty hosta P99 (do ok. 50 MB) i P90 do P95 memory.peak podczas przycinania pamięci podręcznej nieużywanych plików. |
com.android.providers.calendar |
UID (<application>) |
70 MB domyślnie (na pierwszym planie) 50 MB perceptible i background |
Aktywny zestaw roboczy jest niezmienny na różnych poziomach pamięci RAM. Budżet w tle zachowuje anonimowy zapas na alarmy przypomnień, jednocześnie przycinając pamięć podręczną stron SQLite podczas synchronizacji. | ||
com.android.traceur |
UID (<application>) |
85 MB domyślnie (pierwszy plan i procesy widoczne) 30 MB background |
Zachowuje pełny zapas 85 MB podczas aktywnego nagrywania i eksportowania śladu (TraceService), a w stanie bezczynności i transmisji ogranicza go do 30 MB. |
||
com.android.providers.media.module (główny) |
Identyfikator zamówienia reklamowego (<process>) |
60 MB | 75 MB | 85 MB | Zapewnia około 10–30 MB wolnego miejsca powyżej P99 anon + swap, jednocześnie odzyskując nieużywaną pamięć podręczną SQLite i plików miniatur. |
com.android.providers.media.module:PhotoPicker |
Identyfikator zamówienia reklamowego (<process>) |
85 MB + 25 bajtów na piksel (~110 MB w przypadku HD+, ~145 MB w przypadku FHD+, ~190 MB w przypadku ~4,5 MP) |
Skaluje się proporcjonalnie do liczby pikseli wyświetlacza (additionalBytesPerDisplayPixel="25"), aby dopasować się do punktu przegięcia zestawu roboczego bitmapy Glide. |
||
com.android.networkstack |
UID (<application>) |
65 MB | 70 MB | 85 MB | Głównie pamięć anonimowa, o rozmiarze odpowiadającym 95 centylowi czasu życia memory.peak, aby pokryć skoki TLS, DNS i DHCP portalu przechwytującego. |
com.android.appsearch.apk |
UID (<application>) |
130 MB | Obejmuje około 100 MB zablokowanej, nieusuwalnej pamięci oraz około 5 MB aktywnego zestawu roboczego hosta, z miejscem na rozszerzenie zablokowanego regionu. Zero błędów ponownego odczytu przy ≥125 MB, gdy pamięć podręczna zimnych stron (do ~330 MB przy P99) jest odzyskiwana. | ||
com.android.phone (TeleService) |
Identyfikator zamówienia reklamowego (<process>) |
120 MB | Zakres dla poszczególnych identyfikatorów PID na udostępnionym identyfikatorze UID 1001. Zestaw roboczy śledzi generację urządzenia, a nie pamięć RAM, więc pojedynczy budżet jest o około 30 MB wyższy niż najwyższy percentyl P99 dla danego modelu (anon + swap). |
||
Jak producenci OEM mogą ponownie skalibrować budżety aplikacji systemowych na swoich urządzeniach
Producenci OEM, którzy dostosowują aplikacje systemowe (np. dodają profile Bluetooth dostawcy, niestandardowe rozszerzenia telefoniczne lub indeksatory multimediów OEM) lub dostarczają dodatkowe wstępnie zainstalowane usługi systemowe, mogą ponownie skalibrować istniejące budżety lub utworzyć nowe pliki *-budget.xml dla swojego portfolio sprzętu.
Zbieranie aktywnych profili pamięci cgroup v2
Dla każdego docelowego procesu systemowego odczytaj jego węzły pamięci cgroup v2 w /sys/fs/cgroup/uid_<uid>/pid_<pid>/ w reprezentatywnych zbiorach zadań i warstwach pamięci RAM:
memory.current: bieżąca pamięć rezydentna obciążająca grupę kontrolną (z wyłączeniem stron udostępnianych przez Zygote)memory.peak: najwyższy poziommemory.currentod początku procesumemory.swap.current: bieżące wykorzystanie skompresowanej przestrzeni wymiany w ZRAMmemory.stat: zestawienie danych dotyczących:anon,file,pgscan,pgsteal,workingset_refault_anoniworkingset_refault_filememory.events: łączna liczba naruszeńhigh(memory.highzdarzenia ograniczania i odzyskiwania)
Podczas agregowania danych telemetrycznych z urządzeń testowych lub urządzeń w terenie oddziel aktywne stany procesów (TOP, FOREGROUND_SERVICE, aktywne powiązania i aktywne synchronizacje) od stanów bezczynności CACHED lub PERSISTENT, aby migawki bezczynności nie zniekształcały obliczeń zestawu roboczego P95 i P99 w dół.
Oddzielenie nieskompresowanej sterty od pamięci podręcznej plików, którą można odzyskać
Porównaj 99 centyl anon + swap z 99 centylem memory.current:
- Usługi, w których dominuje pamięć podręczna plików (
file≫anon, takie jak dostawcy baz danych, indeksatory wyszukiwania i demony logów): ustawandroid:maxMb, aby zapewnić wygodny margines (zwykle od 1,5 raza do 4 razy) powyżej P99anon + swaporaz okno robocze dla stron z często używanymi plikami. Ta konfiguracja umożliwia jądru ciągłe usuwanie nieużywanych, niezmapowanych stron plików bez wpływu na anonimową stertę. Jeśli wartośćunevictablelubfile_mappedjest duża (jak w przypadku zablokowanej pamięci lub baz danych mapowanych w pamięci), uwzględnij ten rozmiar również w zestawie roboczym. - Usługi zdominowane przez anonimowe sterty lub usługi o charakterze impulsowym (
anonstanowi większośćmemory.current, np. stosy sieciowe lub stosy połączeń): ustaw rozmiarandroid:maxMbtak, aby obejmował od 90 do 95 centylamemory.peak, dzięki czemu przejściowe impulsy nie będą wyzwalać synchronicznej wymiany ZRAM w wątkach wrażliwych na opóźnienia. - Procesy interfejsu intensywnie korzystające z wyświetlacza (takie jak selektory, galerie czy pomocnicy interfejsu systemowego): zmierz podstawowe wykorzystanie pamięci przez procesy niezwiązane z interfejsem oraz przyrostowe wykorzystanie pamięci przez bitmapy na megapiksel na wyświetlaczach o najniższej i najwyższej rozdzielczości, a następnie określ budżet za pomocą
android:maxMbiandroid:additionalBytesPerDisplayPixel.
Czyszczenie pamięci na urządzeniach fizycznych
Zanim przeznaczysz nowy budżet na kompilację, użyj am memory-limiter manual (lub zapisz bezpośrednio w węźle cgroup memory.high procesu w kompilacji userdebug), aby przetestować limity kandydatów podczas automatycznych testów CUJ:
# Override memory.high for a specific PID to 60 MB during a CUJ test
adb shell am memory-limiter manual <PID> 60MBMonitoruj memory.stat i memory.events przed i po głównej ścieżce użytkownika:
- Budżet jest za niski:
workingset_refault_anonlubworkingset_refault_filegwałtownie rośnie podczas CUJ,memory.swap.currentstale rośnie podczas aktywnej pracy na pierwszym planie lub następuje regresja klatki CUJ lub opóźnienia Binder. Zwiększ budżet powyżej domyślnego punktu przegięcia. - Budżet jest za wysoki:
memory.events:highpozostaje na poziomie0nawet po intensywnym przesyłaniu strumieniowym plików lub indeksowaniu, afilewmemory.statrośnie bez ograniczeń w przypadku stron rzadko odwiedzanych. Zmniejsz budżet, aby zbliżyć go do aktywnego zestawu roboczego. - Budżet jest dobrze skalibrowany: podczas normalnego działania wartość
memory.currentw stanie ustalonym utrzymuje się poniżejmemory.high. Podczas intensywnego korzystania z plików lub pamięci podręcznejpgstealodzyskuje strony nieużywanych plików, podczas gdyworkingset_refault_anonpozostaje blisko zera, a opóźnienie CUJ nie ulega zmianie.