В предыдущих разделах мы рассмотрели, как ядро управляет памятью в масштабах всей системы. В этой главе мы углубимся в изучение групп управления памятью (memcg) — механизма, который Android использует для разделения и управления использованием памяти отдельными приложениями.
Понимание memcg имеет решающее значение, поскольку именно на этом уровне система может целенаправленно освобождать память для отдельных приложений или устанавливать для них ограничения на использование памяти, что позволяет осуществлять упреждающее управление объемом памяти, используемой приложениями.
Группы управления памятью (memcg)
Группы управления (cgroups) — это функция ядра Linux, позволяющая организовывать процессы в иерархические группы и распределять между ними системные ресурсы (такие как ЦП, память и ввод-вывод). memcg — это контроллер cgroup, специально предназначенный для управления памятью.
Ключевые концепции МЭМСГ
- Заряд Memcg : Когда процесс в Memcg выделяет страницу памяти (анонимную или файловую), эта страница «заряжается» Memcg. Общий заряд Memcg — это сумма всех страниц, используемых всеми процессами внутри него.
- Иерархический учет : использование памяти учитывается на всех уровнях дерева. Использование памяти в дочерней группе cgroup также учитывается в использовании памяти родительской группы.
- Ограничения памяти : Каждый параметр memcg может иметь ограничения (например,
memory.maxилиmemory.high), превышение которых запускает процесс высвобождения памяти или даже механизм завершения процесса при нехватке памяти, независимо от глобальной системной памяти. - Освобождение памяти для каждого memcg : Когда memcg превышает свой лимит или когда системе требуется память, ядро может выбрать конкретный memcg для освобождения памяти. Это означает вытеснение его файловых страниц или замену его анонимных страниц на ZRAM.
Иерархия memcg в Android
Android использует определенную иерархию для управления процессами приложений. Эта структура позволяет системе применять различные политики к различным типам приложений (например, к приложениям на переднем плане и в фоновом режиме).

-
/sys/fs/cgroup/apps/: Корневая директория для всех приложений Android. -
uid_<UID>/: Каталог для идентификатора пользователя каждого приложения. Все процессы, принадлежащие одному и тому же пакету приложения, используют эту группу. -
pid_<PID>/: Каталог для каждого отдельного процесса и всех дочерних процессов, созданных на его основе. Это позволяет осуществлять точный контроль и учет для приложений с несколькими процессами.
Практическое занятие: изучение MEMCG.
В этом упражнении вы найдете каталог memcg для MemoryLab и понаблюдаете за его использованием памяти в режиме реального времени.
1. Запустите MemoryLab
Убедитесь, что MemoryLab запущен на вашем устройстве.
2. Найдите memcg MemoryLab.
Сначала получите PID запущенного приложения MemoryLab:
adb shell pidof com.android.memorylab
# Example output: 11672
Теперь найдите каталог cgroup. Вы можете найти его по адресу /proc/<PID>/cgroup :
adb shell cat /proc/11672/cgroup
# Example output: 0::/apps/uid_10274/pid_11672
В cgroup v2 путь после 0:: представляет иерархию memcg относительно /sys/fs/cgroup . Таким образом, полный путь — /sys/fs/cgroup/apps/uid_10274/pid_11672/ .
3. Прочитайте статистику memcg.
Перейдите в указанную директорию и посмотрите на ключевые файлы:
# Current memory usage (in bytes)
adb shell cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.current
# Example output:
# 1591324672
memory.current отображает общий объем памяти (в байтах), в данный момент выделенный для этой группы ресурсов.
# 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 предоставляет следующую разбивку: * anon : объем анонимной памяти (кучи, стеки). * file : объем памяти, поддерживаемой файлами (кэш страниц). * swap : объем памяти, выгруженной в ZRAM.
4. Наблюдайте за изменениями.
- Откройте MemoryLab .
- Обратите внимание на значение параметра
memory.current. - Нажмите несколько раз кнопку «Выделить память Java (10 МБ)» .
- Прочитайте
memory.currentеще раз. Вы должны увидеть, как оно увеличивается примерно на 10 МБ при каждом обращении к памяти. - Нажмите «Выделить битовые карты» . Проверьте файл
memory.stat, чтобы увидетьanonувеличение.
Проактивное высвобождение памяти с помощью memory.reclaim
Мощной особенностью memcg (v2) является файл memory.reclaim . Запись значения в этот файл указывает ядру немедленно попытаться освободить соответствующий объем памяти из этого memcg и любых memcg, находящихся под ним .
Как Android использует memory.reclaim
Функция CachedAppOptimizer в Android использует этот инструмент для максимального высвобождения памяти из приложений после их зависания . Android App Freezer гарантирует, что кэшированные приложения потребляют как можно меньше оперативной памяти, когда они не запущены. Когда приложение переходит в фоновый режим и зависает, система записывает текущее использование памяти приложением в файл memory.reclaim . Это заставляет ядро вытеснить все возможные страницы файлов и заменить все анонимные страницы на ZRAM, минимизируя объем памяти, занимаемый приложением.
Вы можете добиться того же "максимального высвобождения" вручную, прочитав memory.current и записав его обратно в 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"
Упражнение: принудительное восстановление и трассировка
Теперь мы заставим ядро освободить память из MemoryLab и зафиксируем эту активность в трассировке Perfetto.
- Подготовка : Убедитесь, что MemoryLab выделил достаточно памяти (для Java и Bitmap).
Начать трассировку : Используйте эту конфигурацию трассировки для захвата событий планирования, освобождения ресурсов и кэширования страниц.
# 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 } } } EOFДавление срабатывания и восстановление :
- В MemoryLab нажмите «Выделить собственную память (1 ГБ)» . Это запустит процесс распределения памяти по всей системе.
В отдельном терминале освободите 200 МБ памяти:
adb shell "echo 200M > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"
Проверка на наличие ошибок : Вернитесь в MemoryLab . Нажмите Thrash Pagecache (Проверка на наличие ошибок) .
Остановить трассировку : Нажмите Ctrl+C в терминале трассировки.
Анализ процесса восстановления данных в Perfetto
Открыв трассировку, вы сможете увидеть в действии как общесистемное, так и индивидуальное высвобождение ресурсов для каждого приложения:

В трассировке обратите внимание на следующее:
- kswapd : Найдите
kswapd0в списке потоков ядра (на скриншоте выше он был вручную закреплен вверху). Вы увидите, как он запускается и работает (зеленые сегменты), пока система пытается найти свободные страницы памяти. - Прямое высвобождение памяти : Посмотрите на потоки процесса
com.android.memorylab. Вы увидите фиолетовые фрагменты событий ftrace (например,mm_vmscan_direct_reclaim_begin), появляющиеся непосредственно под треком планирования потока. Это указывает на то, что сам поток приложения застрял, ожидая, пока ядро освободит страницы памяти. - Счетчики RSS и Swap :
-
mem.rss.anon: Значение увеличивается по мере нажатия кнопок распределения. -
mem.swap: Постоянно увеличивается, посколькуkswapdи собственные потоки приложения (при условии прямого освобождения памяти) сжимают эти анонимные страницы в ZRAM.
-
- memcg Reclaim : Если вы увеличите масштаб в момент запуска ручного восстановления, вы увидите резкое падение значений
rss.anonиrss.file, сопровождаемое событиямиmm_vmscan_memcg_reclaim.
← В масштабах всей системы | ↑ Вверх | Взаимодействие kswapd и lmkd →