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): gdy jest włączona flaga funkcjicom.android.server.am.system_process_memory_budgets(domyślnie wyłączona w pierwszej wersji Androida 17), samoczynnie dostrajające się rezerwacje cgroup v2memory.min(ustawione na 90% ustalonego zestawu roboczego każdego demona i ograniczone globalnie do 10% całkowitej pamięci RAM) chronią krytyczne interaktywne demony i procesy powłoki (system_server,surfaceflinger, graficznycomposerHAL,audioserver,cameraserver,netd,SystemUIi program uruchamiający ekranu głównego) pod obciążeniem 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 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 | Poziomy ochrony demonów systemowych | Limity pamięci aplikacji systemowych |
|---|---|---|
| Usługa platformowa | SystemMemoryBudgetManager |
MemoryLimiter |
| Kontrola cgroup v2 | memory.min – minimalna cena docelowa (ochrona twarda) |
memory.high – pułap miękkiego odzyskiwania |
| Procesy docelowe | Interaktywne demony 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 zawieszaniu się wybudzania przy obciążeniu pamięci podczas zastępowania statycznych list plików PinnerService mlock |
Stale usuwa nieużywaną pamięć podręczną plików jednorazowych i stertę po serii, 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:
- Podstawowy wycinek systemu (
/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 przez zestaw roboczy. 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 pomocnicze podprocesy (takie jak :screenshot) nie zużywają budżetu ochrony.
Zasada samodzielnego dostrajania i 3-poziomowa delegacja cgroup
Zestaw roboczy interaktywnego demona różni się znacznie w zależności od formatu i pojemności pamięci RAM (np. SystemUI używa około 45 MB na zegarku, 60 MB na telefonie z 6 GB pamięci i 180 MB na telefonie z 16 GB pamięci), 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 mogą zostać usunięte. Przed zastosowaniem współczynnika 90% odejmowana jest niezmapowana pamięć podręczna plików (max(0, file - shmem - file_mapped) z memory.stat), dzięki czemu pamięć podręczna skanowania pakietów rozruchowych ani jednorazowe odczyty zasobów nie zawyżają wartości minimalnych. |
| 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 proporcjonalnie zmniejszana o ten sam współczynnik. |
| Częstotliwość odświeżania | 1, 4, 16 i 64 s po uruchomieniu, a następnie co 30 minut | 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 typu liść jest ograniczona przez memory.min skonfigurowane w jej nadrzędnych i nadrzędnych grupach cgroup. Ustawienie memory.min tylko w katalogu liścia pid_<pid>, gdy uid_<uid>/memory.min ma wartość 0, nie zapewnia żadnej ochrony.
Z drugiej strony ustawienie stałej wartości memory.min tylko na najwyższym poziomie /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 dodatkowym, które współdzielą uid_1000 (android.uid.system), na przejęcie ochrony od 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_*liści, dzięki czemu niezarządzane procesy współdzielące ten identyfikator 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 wycinkach 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 pojedynczym 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ło na zmniejszenie zużycia pamięci przezSystemUIna zegarku z ok. 45 MB do ok. 5–10 MB, a przezaudioserverna składanym urządzeniu z 16 GB pamięci – z 15 MB do 2 MB przy minimalnym zużyciu 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 10% najmniej aktywnych procesów każdego demona i utrzymujeSystemUI,surfaceflingeriaudioserverna poziomie 90% bez żadnych anonimowych błędów i bez wzrostu liczby zakończeń procesówlmkd.
memory.min zapewnia twardą rezerwację fizyczną, która odpowiada ochronie przypinania PinnerService mlock, którą zastępuje. Zaletą jest to, że LRU jądra (a nie statyczna lista plików) wybiera, które strony gorącego 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 elementu config_defaultPinnerServiceFiles wymaga skoordynowania 2 zachowań podczas uruchamiania:
- Jednorazowe wstępne wczytywanie w tle podczas uruchamiania (
PinnerService): pomijaniePinnerServicemlockbez wstępnego wczytywania pamięci podręcznej strony powoduje przeniesienie odczytów z dysku plikówSystemUIAPK, ODEX i VDEX o rozmiarze ok. 95 MB do ścieżki synchronicznego uruchamiania, gdy uruchamia sięSystemUI(OnBootPhase_550), co wydłuża czas uruchamiania o 300–450 ms . Wywoł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ć szybkie uruchamianie, a jednocześnie nie pozostawia żadnych trwale zablokowanych bajtów, dzięki czemu strony uruchamiane „na zimno” można później usunąć. - Wykluczanie niezmapowanej pamięci podręcznej plików i odzyskiwanie pamięci podręcznej skanowania pakietów podczas uruchamiania:
podczas uruchamiania
PackageManagerServiceskanuje zainstalowane pliki APK, pozostawiając od 1,5 do 2,5 GB niezmapowanej pamięci podręcznej stron plików, która jest przypisana do grupy kontrolnejsystem_server. Inne chronione procesy (takie jakSystemUIodczytujący pliki zasobów 3D na urządzeniach z XR) mogą również zachowywać niezmapowaną pamięć podręczną plików z jednorazowych odczytów. Jeśli ta pamięć podręczna zostanie uwzględniona w obliczeniachmemory.min, może nasycić globalny limit 10% pamięci RAM i proporcjonalnie skalować w dół dolne limity ochronysurfaceflinger,SystemUIiaudioserver. Aby zapobiec wyczerpaniu limitu,SystemMemoryBudgetManagerodczytujememory.statdla każdego chronionego procesu, 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.reclaimsystem_serverw jądrach Linux 6.2 i nowszych, aby proaktywnie usunąć nieaktywną pamięć podręczną skanowania pakietów bez zamiany anonimowej sterty lub usuwania 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 do propagowania 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 z włączonym MGLRU wymagają 3 portów upstream w mm/vmscan.c i mm/memcontrol.c, aby puste pośrednie grupy kontrolne slice i UID przekazywał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ą włączać, dostosowywać i wyłączać SystemMemoryBudgetManager za pomocą tych flag funkcji i nakładek zasobów platformy:
com.android.server.am.system_process_memory_budgets(flaga funkcji aconfig): włączaSystemMemoryBudgetManageri pomija domyślne przypinanie systemuPinnerServicei ekranu głównegomlock(domyślnie wyłączone w pierwszej wersji Androida 17).config_systemMemoryBudgetLowKb(liczba całkowita, KB):0(domyślnie) ogranicza sumę wszystkich wartościmemory.mindo 10% całkowitej fizycznej 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 kamery (android.hardware.perceptionhostiauracamerahal), a urządzenie z Androidem na komputery może chronić demony HAL dźwięku i kamery USB (dras.core.audioiandroid.hardware.camera.provider-usb-service.android-desktop):<!-- XR device overlay --> <string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses"> <item>android.hardware.perceptionhost</item> <item>auracamerahal</item> </string-array> <!-- Android Desktop device overlay --> <string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses"> <item>dras.core.audio</item> <item>android.hardware.camera.provider-usb-service.android-desktop</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 adb shell dumpsys system_server_dumper --name SystemMemoryBudgetManager 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 te procesy 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 aktywności kswapd i procesu zabijania procesów w przypadku małej ilości pamięci (lmkd) w całym systemie.
Jeśli nie zostaną ograniczone, procesy systemowe w tle 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.highcgroup czyste strony z plikami z odczytów jednorazowych pozostają na zawsze przypisane do grupy kontrolnej procesu, zużywając setki megabajtów fizycznej pamięci RAM. - Zachowywanie 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ą zachowywać nieaktywne anonimowe strony 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 nieaktywne 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 plików „cold” w zawsze włączonych usługach systemowych 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 budżety referencyjnych 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). - Budżet 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 RAM-tiered do domyślnego procesu MediaProvider (<process>) i display-scaled budget do procesu UI :PhotoPicker. Każdy atrybut android:maxDeviceRamMb pasuje do urządzeń, 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 nieograniczonej domyślnej 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>
Gdy deklarujesz <processes> w pliku manifestu budżetu, podaj wpisy <process> tylko dla procesów, które deklarują <memory-budget> (jeśli domyślny proces aplikacji deklaruje niestandardową nazwę android:process w <application>, podaj tę nazwę w elemencie <process android:process="...">, zamiast używać nienazwanego tagu <process />). Podczas kompilacji pliku APK aapt2 automatycznie dodaje wpisy <process> dla wszystkich procesów bez budżetu zadeklarowanych przez komponenty w pliku manifestu łączonego i w razie potrzeby, aby zapewnić zgodność z Androidem 17, wstawia element <memory-budget android:maxMb="1000000" /> w ramach <application>, dzięki czemu budżety poszczególnych procesów pozostają odizolowane od procesów docelowych.
Jak skalibrowano budżety referencyjne AOSP na urządzeniach Google Pixel
Budżety 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 fizycznych urządzeń memory.high w ramach głównych ścieżek użytkownika.
Analiza danych telemetrycznych cgroup v2 w całej flocie
Okresowo zbierano migawki cgroup v2 na wszystkich urządzeniach i dzielono je według poziomu fizycznej pamięci RAM (≤ 8 GB, 12 GB i ≥ 16 GB) oraz grupy 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 rezydentnej 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, które są 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 budżet obejmuje tę wartość minimalną oraz zestaw roboczy hosta, z miejscem na rozszerzenie zablokowanego regionu. - Odfiltrowanie rozcieńczenia w stanie bezczynności: mechanizmy dostarczania 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 ok.1 MB do 3 MB pamięci. Obliczanie nieprzetworzonych percentyli we wszystkich zrzutach znacznie osłabia 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 stanu podstawowego 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 P95 i P99 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: okresowe próbkowanie może pomijać krótkotrwałe skoki o wysokiej alokacji (np. 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 określone na podstawie danych telemetrycznych i skalibrować budżety proporcjonalne do wyświetlania (takie jak :PhotoPicker) oraz budżety w tle zależne od stanu (takie jak Traceur), przeprowadzono testy memory.high na żywo na urządzeniach fizycznych z pamięcią RAM od 6 GB do 16 GB:
- 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. Próg zerowego błędu ponownego odczytu to próg, przy którym jądro odzyskuje nieużywaną 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świetlacza 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 odzyskiwania pamięci ART w stanach tła: w cgroup v2 strony obrazu frameworka i
boot.artudostępniane przez Zygote pozostają przypisane do cgroup Zygote i są traktowane jako przestrzeń odporna podczas odzyskiwania pamięci ART CMC (Concurrent Mark-Compact). 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 szamotania procesów ani aktywności wymiany.
Rysunek 6. Znajdowanie punktu zerowego podczas CUJ.
Podsumowanie budżetów aplikacji systemowych w ramach referencyjnego projektu AOSP
Poniższa tabela 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 (~75–110 MB) i >2x 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 zimnych 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 na HD+, ~145 MB na FHD+, ~190 MB na ~4,5 MP fold) |
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 obsłużyć skoki obciążenia związane z 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 ponownych przy ≥125 MB podczas odzyskiwania pamięci podręcznej zimnych stron (do ~330 MB przy P99). | ||
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 P99 na model 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.
Zbieraj aktywne profile pamięci cgroup v2
W przypadku każdego procesu systemu docelowego odczytaj węzły pamięci cgroup v2 w /sys/fs/cgroup/uid_<uid>/pid_<pid>/ w reprezentatywnych zadaniach i warstwach pamięci RAM:
memory.current: bieżąca pamięć rezydentna obciążająca cgroup (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.
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 zdominowane przez pamięć podręczną 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śliunevictablelubfile_mappedjest duży (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ń): rozmiarandroid:maxMbpowinien obejmować od 90 do 95 centylamemory.peak, aby chwilowe impulsy nie powodowały synchronicznej wymiany ZRAM w wątkach wrażliwych na czas oczekiwania. - Procesy interfejsu intensywnie korzystające z wyświetlacza (np. selektory, galerie lub pomocnicze elementy interfejsu systemu): zmierz podstawowe wykorzystanie pamięci przez elementy inne niż interfejs oraz przyrostowe wykorzystanie pamięci przez bitmapę 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 CUJ i po nim:
- 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:highpozostaje0nawet po intensywnym strumieniowaniu plików lub indeksowaniu, afilewmemory.statrośnie bez ograniczeń w przypadku stron nieaktywnych. Zmniejsz budżet, aby dopasować go do aktywnego zbioru roboczego. - Budżet jest dobrze skalibrowany: podczas normalnego działania wartość
memory.currentw stanie ustalonym utrzymuje się poniżejmemory.high. Podczas intensywnego przesyłania plików lub zapełniania pamięci podręcznejpgstealodzyskuje strony nieużywanych plików, podczas gdyworkingset_refault_anonpozostaje blisko zera, a opóźnienie CUJ nie ulega zmianie.