Основные понятия

Чтобы понять, как используется память в Android, необходимо взглянуть на систему с нескольких разных точек зрения, начиная от высокоуровневых объектов Java и заканчивая низкоуровневыми страницами ядра.

Обрыв производительности

Использование памяти само по себе не является ни хорошим, ни плохим явлением; важно то, для чего вы её используете. Однако, по мере приближения к пределу доступной памяти на устройстве, производительность в конечном итоге резко падает.

График, демонстрирующий стабильную производительность до достижения определенного порога использования памяти, за которым следует резкое снижение, поскольку система начинает интенсивно использовать память и завершать процессы.

When you are far away from the cliff, adding a small amount of memory usage (eg, 50MB) may have no perceptible impact on performance. However, a cliff is reached when the available RAM is exhausted. At this point, the operating system must start paging out memory and killing processes to free up space. Beyond this cliff, even a small increase in memory use can lead to significant "thrashing," where the device becomes unresponsive or appears to reboot because critical processes are killed.

Стек Android

Каждый уровень стека Android имеет собственное уникальное представление о памяти:

Блок-схема стека памяти Android, показывающая приложения Java вверху, ART под ними и ядро ​​Linux с физическими страницами внизу.

  1. Приложения (Java/Kotlin) : Разработчики в основном видят объекты Java, размещаемые в куче Java.
  2. Android Runtime (ART) : ART управляет кучей Java/Kotlin, используя страницы виртуальной памяти из ядра. Ранее был известен как Dalvik (эти термины иногда используются взаимозаменяемо).
  3. Ядро Linux : Ядро воспринимает память в терминах физических и виртуальных страниц . Традиционно это 4 КБ, но Android также поддерживает размеры страниц до 16 КБ. Ядро ничего не знает об «объектах Java».

Чтобы оптимизировать использование памяти, необходимо либо оптимизировать её на собственном уровне (например, загружать меньше растровых изображений), либо понимать работу нижестоящих уровней, чтобы увидеть, как выделение памяти на высоком уровне влияет на использование физических страниц.

Типы памяти: анонимная и файловая.

Прежде чем углубляться в то, как операционная система освобождает память, необходимо понимать две основные категории страниц памяти в Linux:

  1. Анонимная память ( anon ) : память, не связанная с файлом на диске. Сюда входит память, выделенная с помощью malloc в C/C++ (например, аллокатор Scudo), и память, выделенная для объектов Java в куче Java/Kotlin. Поскольку эта память не имеет исходного файла, к которому можно было бы вернуться, ОС должна либо хранить её в ОЗУ, либо сжимать и выгружать в ZRAM при нехватке памяти.
  2. Память, поддерживаемая файлами ( file ) : Память, которая отображается непосредственно из файла на носителе. Сюда входят исполняемый код (файлы DEX, собственные библиотеки .so ) и шрифты.

Файлы, отображаемые в память (mmap)

В Android используется mmap для отображения как файловой, так и анонимной памяти в адресное пространство процесса.

При работе с памятью, поддерживаемой файлами, страницы дополнительно классифицируются на следующие категории:

  • Очищенная память : страницы, отображенные из файла, которые не были изменены. Если системе требуется больше памяти, ядро ​​может просто удалить эти страницы, поскольку их можно будет загрузить непосредственно из файла на хранилище позже.
  • Грязная память : страницы, измененные процессом. Их нельзя удалить; они ведут себя как анонимная память и должны храниться в ОЗУ или быть перемещены в ZRAM.

Android prefers file formats (like DEX) that are amenable to memory mapping, allowing the system to easily reclaim clean memory under pressure. Because anonymous and dirty memory cannot be simply dropped, high private dirty/anon memory is the primary cause of system thrashing and Low Memory Killer (LMK) interventions. If your application uses a lot of private dirty memory, you are directly contributing to the performance cliff.

Модель процесса зиготы

Android минимизирует затраты на запуск новых приложений, используя процесс, называемый Zygote .

  1. Zygote запускается при загрузке системы и предварительно загружает в свою память общие классы и ресурсы фреймворка. С точки зрения ядра, это становится анонимной «грязной» памятью , но она уникальна для процесса Zygote.
  2. При запуске нового приложения система создает дочерний процесс Zygote.
  3. Новый дочерний процесс наследует память Zygote, используя общее отображение с семантикой копирования при записи (COW) .

Диаграмма, иллюстрирующая процесс Zygote, совместно использующий страницы памяти с вновь созданными дочерними процессами с помощью семантики Copy-on-Write.

As long as the child process only reads the memory inherited from Zygote, the physical memory pages remain shared between all processes. When a child process modifies a shared page, the kernel transparently creates a private copy of that page for the process. This model allows many processes to share a large portion of their memory—especially the framework code and resources—significantly reducing the overall system memory footprint.

RSS, PSS и USS

Поскольку память в значительной степени разделяется между процессами — в основном, в рамках модели Zygote — существует три основных способа учета использования памяти процессом:

Диаграмма, визуализирующая разницу между размером резидентного набора (RSS), пропорциональным размером набора (PSS) и уникальным размером набора (USS), показывающая, как учитываются страницы разделяемой памяти.

  • RSS (Resident Set Size) : общее количество страниц, находящихся в оперативной памяти процесса. Этот показатель завышает оценку использования, поскольку учитывает совместно используемые страницы несколько раз (по одному разу для каждого процесса, который их использует).
  • PSS (Proportional Set Size) : Общий объем памяти, уникальный для процесса, плюс его пропорциональная доля общей памяти. Если страница используется пятью процессами, каждый процесс оплачивает 1/5 часть этой страницы.
  • USS (Unique Set Size) : объем памяти, уникальный для данного процесса. Это объем памяти, который будет возвращен системе, если процесс будет завершен.

Какие метрики использовать и когда?

Выбор подходящей метрики памяти зависит от того, что вы пытаетесь измерить, и от среды, в которой вы это измеряете.

Метрическая система Лучше всего подходит для Ключевая сила Главный недостаток
ПСС Снимки памяти в масштабе всей системы Точно определяет пропорции общей памяти. Сумма PSS равна общему объему используемой памяти. Колебания зависят от других запущенных процессов. Плохо подходит для сравнения производительности конкретного приложения во времени.
RSS Отслеживание работы отдельного приложения во времени, полевая телеметрия. Стабильный, независимый от других процессов. Быстрый и недорогой сбор. Завышает оценку общего объема использования, поскольку учитывает общую память несколько раз.
USS Оценка последствий прекращения процесса Показывает, сколько именно памяти будет освобождено при завершении работы приложения. Полностью игнорирует общую память.

PSS (пропорциональный размер набора)

  • Лучше всего подходит для: создания полного снимка памяти системы определенного устройства в заданный момент времени.
  • Почему: PSS обладает полезным математическим свойством, заключающимся в том, что сумма PSS для всех запущенных процессов равна общему объему памяти, используемой процессами в системе. Это позволяет идеально распределять общую память без двойного учета.
  • Когда следует избегать: Не используйте PSS для сравнения конкретного процесса в двух разных снимках, в разное время или на разных устройствах. Поскольку PSS зависит от того, сколько других процессов в данный момент используют общие страницы с вашим процессом, значение PSS вашего приложения может колебаться, даже если фактическое поведение приложения остается совершенно неизменным.

RSS (Resident Set Size)

  • Лучше всего подходит для: отслеживания использования памяти отдельным приложением с течением времени, в рамках различных критически важных пользовательских сценариев (CUJ) или сравнения версий приложений. Это также наиболее практичный выбор для полевой телеметрии.
  • Почему: RSS более стабилен, чем PSS. Он показывает, какая часть памяти отображается в вашем процессе, независимо от того, что делают другие приложения. Кроме того, сбор данных RSS обходится недорого. Для измерения PSS или USS требуется сканирование сложных структур управления памятью ядра и получение блокировок, что может привести к проблемам с производительностью системы (например, к зависанию пользовательского интерфейса), если сбор данных происходит слишком часто.
  • Рекомендации по проведению полевых измерений: При сборе данных с устройств в реальных условиях обычно лучше всего измерять анонимный RSS + обмен данными (ZRAM) .
    • Why anonymous only? File-backed pages (like code, app resources, fonts, or other memory-mapped files) can be evicted by the kernel at any time due to system-wide pagecache pressure. Including file pages in metrics to track a particular app introduces noise from the OS's memory management decisions that may be driven by memory pressure from other apps or the system. Conversely, anonymous memory (like the Java and native heaps) is directly controlled by your app.
    • Why add swap? Under memory pressure, the OS will compress anonymous pages and move them to ZRAM (swap). If you only measure resident anonymous memory, your metrics might falsely show a "decrease" in memory usage simply because the system was under pressure and swapped out your pages. Adding swapped memory ensures you account for all the anonymous memory your app has allocated, whether it currently resides in RAM or ZRAM.

USS (Уникальный размер набора)

  • Наилучший вариант для: определения непосредственного влияния остановки процесса на систему.
  • Почему: USS точно отображает тот объем памяти, который будет немедленно освобожден и возвращен системе, если процесс будет завершен (например, демоном Low Memory Killer, lmkd ).
  • Как использовать: Этот показатель очень ценен при оценке постоянно работающих процессов или фоновых служб. Если вы оцениваете общесистемные затраты на фоновый процесс, USS точно покажет, сколько памяти система жертвует исключительно для поддержания работы этого конкретного процесса.

↑ Вверх | Инструменты →