W poprzednich sekcjach omówiliśmy, jak jądro zarządza pamięcią w całym systemie. W tym rozdziale przyjrzymy się bliżej grupom kontroli pamięci (memcg), czyli mechanizmowi, którego Android używa do dzielenia i kontrolowania wykorzystania pamięci przez poszczególne aplikacje.
Zrozumienie memcg jest kluczowe, ponieważ to na tym poziomie system może kierować się do poszczególnych aplikacji w celu odzyskania pamięci lub ustawienia limitów pamięci, co umożliwia proaktywne zarządzanie wykorzystaniem pamięci przez aplikacje.
Grupy kontroli pamięci (memcg)
Grupy kontroli (cgroups) to funkcja jądra systemu Linux, która umożliwia organizowanie procesów w hierarchiczne grupy i rozdzielanie między nie zasobów systemowych (takich jak procesor, pamięć i wejście/wyjście). memcg to kontroler cgroup przeznaczony specjalnie do zarządzania pamięcią.
Kluczowe pojęcia dotyczące memcg
- Obciążenie memcg: gdy proces w memcg przydziela stronę pamięci (anonimową lub wspieraną przez plik), ta strona jest "obciążana" na rzecz memcg. Całkowite obciążenie memcg to suma wszystkich stron używanych przez wszystkie procesy w tej grupie.
- Hierarchiczne rozliczanie: wykorzystanie pamięci jest rozliczane w górę drzewa. Obciążenie w podrzędnej grupie cgroup jest też uwzględniane w wykorzystaniu jej grupy nadrzędnej.
- Limity pamięci: każda grupa memcg może mieć limity (np.
memory.maxlubmemory.high), które w przypadku przekroczenia mogą spowodować odzyskanie pamięci lub nawet wyłączenie procesu przez OOM, niezależnie od globalnej pamięci systemowej. - Odzyskiwanie pamięci w memcg: gdy memcg przekroczy limit lub gdy system potrzebuje pamięci, jądro może skierować się do konkretnej grupy memcg w celu odzyskania pamięci. Oznacza to usunięcie stron plików lub przeniesienie stron anonimowych do ZRAM.
Hierarchia memcg w Androidzie
Android używa określonej hierarchii do zarządzania procesami aplikacji. Ta struktura umożliwia systemowi stosowanie różnych zasad do różnych typów aplikacji (np. działających na pierwszym planie i w tle).

/sys/fs/cgroup/apps/: katalog główny wszystkich aplikacji na Androida.uid_<UID>/: katalog dla każdego identyfikatora użytkownika aplikacji. Wszystkie procesy należące do tego samego pakietu aplikacji współdzielą tę grupę.pid_<PID>/: katalog dla każdego procesu i jego procesów potomnych. Umożliwia to szczegółową kontrolę i rozliczanie aplikacji z wieloma procesami.
Ćwiczenie praktyczne: poznawanie memcg
W tym ćwiczeniu znajdziesz katalog memcg dla MemoryLab i zaobserwujesz jego obciążenie pamięci w czasie rzeczywistym.
1. Uruchom MemoryLab
Upewnij się, że na urządzeniu działa MemoryLab.
2. Znajdź memcg MemoryLab
Najpierw pobierz PID działającej aplikacji MemoryLab:
adb shell pidof com.android.memorylab
# Example output: 11672
Teraz znajdź jej katalog cgroup. Znajdziesz go w /proc/<PID>/cgroup:
adb shell cat /proc/11672/cgroup
# Example output: 0::/apps/uid_10274/pid_11672
W cgroup v2 ścieżka po 0:: reprezentuje hierarchię memcg względem /sys/fs/cgroup. Pełna ścieżka to /sys/fs/cgroup/apps/uid_10274/pid_11672/.
3. Odczytaj statystyki memcg
Otwórz ten katalog i sprawdź kluczowe pliki:
# Current memory usage (in bytes)
adb shell cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.current
# Example output:
# 1591324672
memory.current pokazuje łączną ilość pamięci (w bajtach) aktualnie obciążonej na rzecz tej grupy cgroup.
# Detailed statistics
adb shell cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.stat | head -n 10
# Example output:
# anon 1585098752
# file 741376
# kernel 5484544
# kernel_stack 393216
# pagetables 4583424
# sec_pagetables 0
# percpu 216
# sock 0
# vmalloc 4096
# shmem 20480
memory.stat zawiera podział: * anon: ilość pamięci anonimowej
(sterty, stosy). * file: ilość pamięci wspieranej przez plik (pamięć podręczna stron). *
swap: ilość pamięci przeniesionej do ZRAM.
4. Obserwuj zmiany
- Otwórz MemoryLab.
- Zanotuj wartość
memory.current. - Kilka razy kliknij Allocate Java Memory (10MB) (Przydziel pamięć Java (10 MB)).
- Ponownie odczytaj
memory.current. Powinna się zwiększyć o około 10 MB po każdym kliknięciu. - Kliknij Allocate Bitmaps (Przydziel bitmapy). Sprawdź
memory.stat, aby zobaczyć wzrostanon.
Proaktywne odzyskiwanie pamięci za pomocą memory.reclaim
Przydatną funkcją memcg (v2) jest plik memory.reclaim. Zapisanie wartości w tym pliku powoduje, że jądro natychmiast próbuje odzyskać tę ilość pamięci z tej grupy memcg i wszystkich grup memcg pod nią.
Jak Android używa memory.reclaim
CachedAppOptimizer w Androidzie używa tej funkcji, aby maksymalnie odzyskać pamięć z aplikacji po ich zamrożeniu. App Freezer w Androidzie dba o to, aby aplikacje w pamięci podręcznej zużywały jak najmniej pamięci RAM, gdy nie są uruchomione. Gdy aplikacja przechodzi w tle i zostaje zamrożona, system zapisuje jej aktualne wykorzystanie pamięci w pliku memory.reclaim. Zmusza to jądro do usunięcia wszystkich możliwych stron plików i przeniesienia wszystkich stron anonimowych do ZRAM, co minimalizuje ilość pamięci zajmowanej przez aplikację.
To samo „maksymalne odzyskiwanie” możesz osiągnąć ręcznie, odczytując memory.current i zapisując ją z powrotem w memory.reclaim:
# Force reclaim of everything
adb shell "cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.current > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"
Ćwiczenie: wymuś odzyskanie pamięci i śledzenie
Teraz zmusimy jądro do odzyskania pamięci z MemoryLab i zarejestrujemy tę aktywność w śladzie Perfetto.
- Przygotowanie: upewnij się, że MemoryLab ma przydzieloną pamięć (Java i bitmapy).
Rozpoczęcie śledzenia: użyj tej konfiguracji śledzenia w tekście, aby rejestrować zdarzenia związane z planowaniem, odzyskiwaniem pamięci i pamięcią podręczną stron.
# Use a config that captures scheduling, reclaim and page cache events adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/reclaim.perfetto-trace <<EOF buffers: { size_kb: 131072 fill_policy: RING_BUFFER } data_sources: { config { name: "linux.ftrace" ftrace_config { ftrace_events: "sched/sched_switch" ftrace_events: "sched/sched_wakeup" ftrace_events: "kmem/rss_stat" ftrace_events: "mm_filemap_add_to_page_cache" ftrace_events: "mm_filemap_delete_from_page_cache" ftrace_events: "vmscan/mm_vmscan_direct_reclaim_begin" ftrace_events: "vmscan/mm_vmscan_direct_reclaim_end" ftrace_events: "vmscan/mm_vmscan_memcg_reclaim_begin" ftrace_events: "vmscan/mm_vmscan_memcg_reclaim_end" symbolize_ksyms: true } } } data_sources: { config { name: "linux.process_stats" process_stats_config { scan_all_processes_on_start: true } } } EOFWywołanie presji i odzyskiwania pamięci:
- W MemoryLab kliknij Allocate Native Memory (1GB) (Przydziel pamięć natywną (1 GB)). Spowoduje to wywołanie presji w całym systemie.
W osobnym terminalu odzyskaj 200 MB:
adb shell "echo 200M > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"
Powrót do aplikacji: wróć do MemoryLab. Kliknij Thrash Pagecache (Refault test) (Wyczyść pamięć podręczną stron (test ponownego błędu)).
Zatrzymanie śledzenia: naciśnij Ctrl+C w terminalu śledzenia.
Analizowanie odzyskiwania pamięci w Perfetto
Po otwarciu śladu możesz zobaczyć, jak działa odzyskiwanie pamięci w całym systemie i w poszczególnych aplikacjach:

W śladzie poszukaj tych elementów:
- kswapd: wyszukaj
kswapd0w sekcji Kernel threads (na zrzucie ekranu powyżej został ręcznie przypięty u góry). Gdy system będzie miał trudności ze znalezieniem wolnych stron, zobaczysz, jak się budzi i działa (zielone fragmenty). - Bezpośrednie odzyskiwanie pamięci: sprawdź wątki procesu
com.android.memorylab. Bezpośrednio pod ścieżką planowania wątku zobaczysz fioletowe fragmenty zdarzeń ftrace (np.mm_vmscan_direct_reclaim_begin). Wskazuje to, że wątek aplikacji jest zablokowany i czeka, aż jądro zwolni strony. - Liczniki RSS i wymiany:
mem.rss.anon: zwiększa się, gdy klikasz przyciski przydzielania pamięci.mem.swap: stale się zwiększa, gdykswapdi własne wątki aplikacji (podlegające bezpośredniemu odzyskiwaniu pamięci) kompresują te strony anonimowe do ZRAM.
- Odzyskiwanie pamięci w memcg: jeśli powiększysz moment, w którym wywołałeś ręczne
odzyskiwanie pamięci, zobaczysz gwałtowny spadek wartości
rss.anonirss.file, któremu towarzyszą zdarzeniamm_vmscan_memcg_reclaim.
← Cały system | ↑ W górę | Interakcja kswapd i lmkd →