Чтобы понять, как используется память на Android, нужно взглянуть на систему с разных точек зрения – от высокоуровневых объектов Java до низкоуровневых страниц ядра.
Резкое падение эффективности
Использование памяти само по себе не является хорошим или плохим; важно то, для чего вы используете память. Однако по мере приближения к пределу доступной памяти на устройстве вы в конечном итоге столкнетесь с резким падением производительности.

Если вы далеко от обрыва, то небольшое увеличение использования памяти (например, на 50 МБ) может не оказать заметного влияния на производительность. Однако когда доступная оперативная память исчерпана, наступает критический момент. В этот момент операционная система должна начать выгружать память и завершать процессы, чтобы освободить место. За этим пределом даже небольшое увеличение объема используемой памяти может привести к значительному снижению производительности, когда устройство перестает отвечать на запросы или перезагружается из-за завершения критических процессов.
Стек Android
У каждого уровня стека Android есть свое уникальное представление памяти:

- Приложения (Java/Kotlin). Разработчики в основном видят объекты Java, размещенные в куче Java.
- Android Runtime (ART). Среда ART управляет кучей Java/Kotlin, используя страницы виртуальной памяти из ядра. Ранее называлась Dalvik (иногда эти термины используются как взаимозаменяемые).
- Ядро Linux. Ядро видит память в виде физических и виртуальных страниц. Обычно их размер составляет 4 КБ, но Android также поддерживает страницы размером 16 КБ. Ядро ничего не знает об объектах Java.
Чтобы оптимизировать память, нужно либо оптимизировать собственный уровень (например, загружать меньше растровых изображений), либо понять, как работают уровни ниже, чтобы увидеть, как высокоуровневые распределения преобразуются в использование физических страниц.
Типы памяти: анонимная и связанная с файлами
Прежде чем перейти к тому, как ОС освобождает память, необходимо понять две фундаментальные категории страниц памяти в Linux:
- Анонимная память (
anon). Память, которая не поддерживается файлом в хранилище. Сюда входит память, выделенная C/C++malloc(например, распределителем Scudo), и память, выделенная для объектов Java в куче Java/Kotlin. Поскольку у этой памяти нет исходного файла, к которому можно вернуться, ОС должна либо хранить ее в ОЗУ, либо сжимать и выгружать в ZRAM при нехватке ресурсов. - Память, связанная с файлами (
file). Память, которая сопоставляется непосредственно с файлом в хранилище. К ним относятся исполняемый код (DEX-файлы, нативные библиотеки.so) и шрифты.
Файлы, отображаемые в память (mmap)
Android использует mmap, чтобы сопоставить память, поддерживаемую файлами, и анонимную память с адресным пространством процесса.
Страницы памяти, поддерживаемой файлами, делятся на следующие категории:
- Чистая память. Страницы, сопоставленные с файлом, который не был изменен. Если системе требуется больше памяти, ядро может просто удалить эти страницы, поскольку позже их можно будет загрузить непосредственно из файла в хранилище.
- Измененная память. Страницы, измененные процессом. Их нельзя удалить. Они ведут себя как анонимная память, и их нужно хранить в ОЗУ или переместить в ZRAM.
В Android предпочтительны форматы файлов (например, DEX), которые поддаются сопоставлению памяти, что позволяет системе легко восстанавливать чистую память под давлением. Поскольку анонимную и измененную память нельзя просто удалить, высокий объем частной измененной или анонимной памяти – основная причина пробуксовки системы и вмешательства Low Memory Killer (LMK). Если ваше приложение использует много частной измененной памяти, это напрямую влияет на производительность.
Модель процесса Zygote
Android минимизирует затраты на запуск новых приложений с помощью процесса Zygote.
- Zygote запускается при загрузке и предварительно загружает в память общие классы и ресурсы фреймворка. С точки зрения ядра это анонимная измененная память, но она уникальна для процесса Zygote.
- Когда запускается новое приложение, система разветвляет процесс Zygote.
- Новый дочерний процесс наследует память Zygote, используя общее сопоставление с семантикой копирования при записи (COW).

Пока дочерний процесс только считывает данные из памяти, унаследованной от Zygote, физические страницы памяти остаются общими для всех процессов. Когда дочерний процесс изменяет общую страницу, ядро прозрачно создает для него частную копию этой страницы. Эта модель позволяет многим процессам совместно использовать большую часть своей памяти, особенно код и ресурсы фреймворка, что значительно снижает общий объем потребляемой памяти системы.
RSS, PSS, USS и memcg
Поскольку память активно используется разными процессами (в основном через модель Zygote), есть несколько способов учета использования памяти процессом. RSS, PSS и USS учитывают страницы, сопоставленные с адресным пространством процесса:

- RSS (Resident Set Size) – общее количество страниц, которые процесс хранит в ОЗУ. Это завышает использование, поскольку общие страницы учитываются несколько раз (по одному разу для каждого процесса, который их использует).
- PSS (Proportional Set Size). Общий объем памяти, выделенный только для процесса, плюс пропорциональная доля общей памяти. Если страница используется пятью процессами, то на каждый из них приходится 1/5 часть страницы.
- USS (Unique Set Size) – объем памяти, выделенный только для этого процесса. Это память, которая будет возвращена системе, если процесс будет завершен.
memcg: собственный учет ядра;
Ядро также ведет собственный учет памяти, используемой каждым процессом, независимо от того, сопоставлена ли эта память. В Android каждый процесс приложения принадлежит собственной группе управления памятью (memcg), вложенной в memcg для всего приложения (см. раздел Иерархия memcg в Android).
При выделении страницы памяти ядро назначает ее группе memcg процесса, который ее выделил. Общая сумма расходов указана в файле memory.current memcg.
Учет memcg отличается от RSS, PSS и USS по нескольким важным параметрам:
- Каждая страница учитывается ровно один раз в memcg процесса, который первым загрузил ее в память. Она остается там, даже если другие процессы позже сопоставляют ту же страницу. Страницы, предварительно загруженные Zygote, относятся к Zygote, а не к вашему приложению. Когда ваше приложение записывает данные на одну из этих страниц, частная копия относится к вашему приложению.
- Включает память, которая не учитывается в показателях таблицы страниц.
memory.current– количество страниц файлов, которые приложение считывает без сопоставления (например, страницы базы данных, считанные с помощьюreadилиpread). - Раздел подкачки отслеживается отдельно. Память, сжатая в ZRAM, перемещается из
memory.currentвmemory.swap.current, поэтому вы можете видеть оба значения. - Это дешево. Счетчики всегда актуальны, и их чтение не блокирует и не сканирует адресное пространство приложения.
- Это ограничение системы. Ограничения и бюджеты памяти, которые Android применяет к приложениям, действуют на уровне memcg.
Графическая память, например буферы за аппаратными битовыми картами, обычно не учитывается в memcg приложения.
Какие показатели использовать и когда?
Выбор подходящего показателя зависит от того, что вы хотите измерить и в какой среде. На устройствах с Android 17 QPR2 (уровень API 37.2) и более поздних версий начните с memcg, когда отслеживаете собственное приложение.
| Показатель | Устройства | Ключевое преимущество | Основной недостаток |
|---|---|---|---|
| memcg | Отслеживание отдельного приложения с течением времени, сравнение с бюджетами | Каждая страница учитывается один раз, включая кеш несопоставленных страниц. Подкачка отслеживается отдельно. Недорого. | Доступно не на всех устройствах с версиями ОС, предшествующими Android 17 QPR2. Общие страницы относятся к процессу, который обратился к ним первым. |
| PSS | Снимки памяти на уровне системы | Точно распределяет общую память. Сумма PSS равна общему объему используемой памяти. | Колеблется в зависимости от других запущенных процессов. Не подходит для сравнения показателей одного приложения за разные периоды времени. |
| RSS | Отслеживание отдельного приложения с течением времени, полевая телеметрия | Стабильная, не зависит от других процессов. Быстрый и дешёвый сбор данных. | Завышает общий объем использования, поскольку общая память учитывается несколько раз. |
| USS | Оценка влияния завершения процесса | Показывает, сколько памяти освободится, если закрыть приложение. | Полностью игнорирует общую память. |
memcg (загрузка группы управления памятью)
- Для чего подходит. Отслеживание использования памяти отдельным приложением в динамике, в рамках алгоритмов достижения цели или в разных версиях приложения на устройствах с Android 17 QPR2 или более поздней версией. Это также показатель, за которым нужно следить при калибровке бюджетов памяти, поскольку это число, которое ограничивают бюджеты.
- Почему. Как и в случае с RSS, значение memcg charge не меняется, когда другие приложения начинают или прекращают использовать страницы совместно с вашим приложением. В отличие от RSS, memcg charge никогда не учитывает страницу дважды, поэтому вы можете сложить значения для всех запущенных процессов приложения. Значение memcg для всего приложения может быть выше этой суммы, поскольку в нем хранятся кэшированные страницы файлов из завершенных процессов, пока ядро не освободит их. Кроме того, в нем учитывается память, которая не входит в RSS, PSS и USS, например несопоставленный кеш файлов. Чтение этого файла не требует больших ресурсов, поскольку ядро обновляет счетчики при загрузке страниц.
- Как прочитать. Прочитайте
memory.current,memory.swap.currentиmemory.statв каталоге memcg процесса (см. статистику memcg). В Android 18 и более поздних версияхdumpsys meminfoтакже выводит их. В приложении используйтеMemoryBudgetManager. - Рекомендации по измерению на местах. По тем же причинам, что описаны в разделе RSS, отслеживайте анонимную память и файл подкачки: поле
anonвmemory.statплюсmemory.swap.current. Полеfileвmemory.statвключает страницы файлов, которые ваше приложение прочитало один раз и которые ядро ещё не освободило, поэтому значениеmemory.currentможет оставаться высоким в течение некоторого времени после того, как ваше приложение перестанет использовать эту память. - На что обратить внимание. Поскольку страницы относятся к процессу, который обратился к ним первым, вашему приложению будет приписана страница общего файла (например, шрифта или общей библиотеки), которую оно прочитало раньше других процессов.
И наоборот, другой процесс может оплачивать страницы, которые также использует ваше приложение. При сравнении полагайтесь на
anonи swap, так как объем памяти, поддерживаемой файлами, может немного отличаться в зависимости от устройства и запуска.
PSS (Proportional Set Size)
- Для чего подходит. Создание полного снимка системной памяти определенного устройства в заданный момент времени.
- Зачем это нужно? У PSS есть полезное математическое свойство: сумма PSS для всех запущенных процессов равна общему объему памяти, используемой процессами в системе. Она идеально распределяет общую память без двойного учета.
- Когда не следует использовать. Не используйте PSS для сравнения определенного процесса в двух разных снимках, в разное время или на разных устройствах. Поскольку PSS зависит от того, сколько других процессов в настоящее время используют общие страницы с вашим процессом, PSS вашего приложения может колебаться, даже если фактическое поведение вашего приложения остается полностью неизменным.
RSS (Resident Set Size)
- Для чего подходит. Отслеживание использования памяти отдельным приложением в течение определенного времени, в разных алгоритмах достижения цели или сравнение версий приложения. Это также наиболее практичный выбор для полевой телеметрии.
- Причина изменений. RSS стабильнее, чем PSS. Она показывает, сколько памяти выделено для вашего процесса, независимо от того, что делают другие приложения. Кроме того, RSS дешево собирать. Для измерения PSS или USS требуется сканировать сложные структуры управления памятью ядра и получать блокировки, что может привести к проблемам с производительностью системы (например, к задержкам в интерфейсе), если сбор данных выполняется слишком часто.
- Рекомендации по измерению в полевых условиях. При сборе данных с устройств в полевых условиях обычно лучше всего измерять анонимный RSS + swap (ZRAM).
- Почему только анонимные? Страницы, связанные с файлами (например, код, ресурсы приложения, шрифты или другие файлы, сопоставленные с памятью), могут быть удалены ядром в любое время из-за нагрузки на кэш страниц на уровне системы. Если учитывать страницы файлов в показателях для отслеживания определенного приложения, это приведет к появлению шума, связанного с решениями ОС по управлению памятью, которые могут быть вызваны нехваткой памяти из-за других приложений или системы. Анонимная память (например, кучи Java и нативного кода) напрямую контролируется вашим приложением.
- Зачем добавлять раздел подкачки? При нехватке памяти ОС сжимает анонимные страницы и перемещает их в ZRAM (подкачка). Если вы измеряете только резидентную анонимную память, ваши показатели могут ложно показывать "уменьшение" использования памяти просто потому, что система находилась под давлением и поменяла ваши страницы. Добавление подкачанной памяти позволяет учесть всю анонимную память, выделенную вашему приложению, независимо от того, находится ли она в ОЗУ или ZRAM.
- По возможности используйте memcg. На устройствах с Android 17 QPR2 и более поздними версиями ОС значение
anonвmemory.statплюсmemory.swap.currentдает аналогичный показатель, который не учитывает анонимные страницы, общие с Zygote, и требует меньше ресурсов для сбора (см. memcg).
USS (Unique Set Size)
- Для чего подходит. Определение того, как завершение процесса повлияет на систему.
- Почему? USS точно отражает объем памяти, который будет немедленно освобожден и возвращен системе, если процесс будет завершен (например, демоном Low Memory Killer,
lmkd). - Как использовать. Этот показатель очень полезен при оценке постоянных процессов или фоновых служб. Если вы оцениваете стоимость фонового процесса на уровне системы, USS точно сообщает, сколько памяти система жертвует исключительно для поддержания этого конкретного процесса.