В Android 17 и более поздних версиях платформа управляет памятью системных процессов с помощью двух дополнительных механизмов Linux cgroup v2, которые работают вместе с общим ограничителем памяти приложений:
- Нижние пороги защиты системных демонов (
SystemMemoryBudgetManagerсmemory.min). Саморегулирующиеся резервированияmemory.minв cgroup v2 (установленные на уровне 90 % от рабочего набора каждого демона и ограниченные на глобальном уровне 10% от общего объема физической ОЗУ) защищают критически важные интерактивные демоны и процессы оболочки (system_server,surfaceflinger, графический HALcomposer,audioserver,cameraserver,netd,SystemUIи главный экран) при нехватке памяти, заменяя статическое закрепление файловPinnerServicemlock. - Ограничения памяти для системных приложений (
<memory-budget>сmemory.high). Мягкие ограничения cgroup v2memory.high, откалиброванные по рабочей нагрузке (обычно от 30 до 190 МБ), объявлены в файлах манифеста для постоянных и фоновых системных приложений (Bluetooth,Nfc,CalendarProvider,Traceur,MediaProvider,:PhotoPicker,NetworkStack,AppSearchиTeleService). Они упреждающе освобождают кэш страниц неиспользуемых файлов и анонимную кучу после пиковой нагрузки, прежде чем возникнет глобальная нехватка памяти.
В таблице ниже сравниваются эти два механизма.
| Параметр | Уровни защиты системных демонов | Максимальный объем памяти системных приложений |
|---|---|---|
| Сервис платформы | SystemMemoryBudgetManager |
MemoryLimiter |
| Управление cgroup v2 | memory.min жесткая минимальная цена |
memory.high: максимальное количество возвращенных показов |
| Целевые процессы | Интерактивные демоны, критичные к задержкам, и интерфейс оболочки (system_server, surfaceflinger, composer HAL, audioserver, cameraserver, netd, SystemUI и приложение Google Home). |
Постоянные и безголовые системные приложения, поставщики контента и фоновые службы, такие как Bluetooth, Nfc, MediaProvider, NetworkStack, AppSearch и TeleService. |
| Модель определения размера | Самостоятельная настройка во время выполнения (90% от установленного рабочего набора, глобальное ограничение в 10% от общего объема ОЗУ) без таблиц настройки для каждого устройства. | Декларативные XML-атрибуты манифеста <memory-budget>, откалиброванные по уровню ОЗУ, состоянию процесса и количеству пикселей на экране |
| Основное преимущество | Предотвращает задержки в работе интерактивного интерфейса и зависания при пробуждении из-за нехватки памяти во время замены списков статических файлов PinnerService mlock. |
Постоянно удаляет неиспользуемый одноразовый кеш файлов и кучу после периода ускорения, чтобы освободить больше физической ОЗУ для активных и кешированных приложений. |
Дополнительную информацию о синтаксисе <memory-budget>, атрибутах XML, API среды выполнения и учете памяти ОС можно найти в статьях Как задать бюджеты памяти для приложений и Как устроена память Android и как устранять неполадки с ней.
Защита критически важных интерактивных демонов с помощью SystemMemoryBudgetManager
SystemMemoryBudgetManager сохраняет процессы, которые находятся непосредственно на интерактивном пути пользователя, в физической оперативной памяти при нехватке памяти. Вместо того чтобы закреплять статический список файлов APK, ODEX и VDEX в памяти с помощью mlock (как PinnerService делает для config_defaultPinnerServiceFiles и config_pinnerHomePinBytes), SystemMemoryBudgetManager назначает динамические нижние границы memory.min cgroup v2 следующим процессам:
- Срез основной системы (
/sys/fs/cgroup/system).system_server,surfaceflinger, HAL графикиcomposer,audioserver,cameraserverиnetd, а также любые нативные системные демоны или демоны поставщика, зависящие от форм-фактора и перечисленные вconfig_systemMemoryBudgetAdditionalSystemProcesses(например,android.hardware.perceptionhostиauracamerahalна XR-устройствах). - Интерактивный фрагмент приложения (
/sys/fs/cgroup/apps). Основной процесс настроенного пакета SystemUI (config_systemUi) и основной процесс приложения, которому назначена роль главного экрана текущего пользователя.
Защита роли Home наряду с config_systemUi обеспечивает охват всех типов устройств. На устройствах, где config_systemUi – это облегченный пакет совместимости (например, на Wear OS, где циферблат, уведомления и карточки работают в приложении Home), пользовательская оболочка по-прежнему получает полную защиту рабочего набора. Если намерение Home разрешается в пакет config_systemUi, процесс защищается один раз. Минимальный уровень защиты получает только основной процесс каждого пакета (processName == packageName), поэтому вспомогательные подпроцессы (например, :screenshot) не расходуют бюджет защиты.
Правила самонастройки и трехуровневое делегирование cgroup
Поскольку рабочий набор интерактивного демона сильно зависит от форм-фактора и объема ОЗУ (например, SystemUI использует около 45 МБ на умных часах, 60 МБ на телефоне с 6 ГБ ОЗУ и 180 МБ на телефоне с 16 ГБ ОЗУ), статические таблицы поиска для каждого устройства ненадежны. Вместо этого SystemMemoryBudgetManager выводит минимальные значения непосредственно из резидентного объема памяти каждого процесса, используя три константы:
| Параметр правила | Значение по умолчанию | Поведение |
|---|---|---|
| Минимальная цена за процесс | 90% от урегулированной суммы memory.current за вычетом кеша несопоставленных файлов |
Защищает анонимную кучу, slab-аллокатор и часто используемый код, оставляя 10% наименее используемых страниц доступными для вытеснения. Кэш несопоставленных файлов (max(0, file - shmem - file_mapped) из memory.stat) вычитается до применения коэффициента 90 %, чтобы кэш сканирования пакетов при загрузке или однократное считывание объектов не завышали минимальные значения. |
| Общее ограничение на объем ОЗУ | 10% от общего объема ОЗУ (config_systemMemoryBudgetLowKb) |
Соответствует ограничению PinnerService config_pinnerMaxPinnedMemoryPercentage. Если сумма 90% минимальных цен превышает 10% от MemTotal, то все минимальные цены пропорционально уменьшаются на один и тот же коэффициент. |
| Частота обновления | Через 1, 4, 16 и 64 секунды после запуска, а затем каждые 30 минут. | Геометрическая серия урегулирования сходится на рабочем наборе каждого демона после запуска без жесткого кодирования задержек прогрева для каждого устройства, а 30-минутное обновление в установившемся режиме отслеживает постепенный рост и сокращение рабочего набора в течение срока службы устройства. |
В контроллере памяти Linux cgroup v2 (mm/memcontrol.c:effective_protection) эффективная защита memory.min дочерней группы cgroup ограничена значением memory.min, настроенным для ее родительской и вышестоящих групп cgroup. Если задать правило memory.min только для конечного каталога pid_<pid>, а для правила uid_<uid>/memory.min задать значение 0, защита не будет работать.
И наоборот, если задать плоский memory.min только для верхнего слоя /sys/fs/cgroup/system, то невостребованная защита будет распределена между всеми дочерними UID и процессами пропорционально (memory_recursiveprot). В результате диагностические или вторичные процессы, использующие uid_1000 (android.uid.system), смогут забирать защиту у surfaceflinger и audioserver.
Чтобы обеспечить точную изоляцию каждого демона, SystemMemoryBudgetManager записывает защиту на всех трех уровнях иерархии cgroup v2 при каждом обновлении, как показано на рисунке 1:
- Дочерний узел процесса (
pid_<pid>/memory.min). Установите значение, равное 90% от минимального рабочего набора процесса (с учетом масштабирования). - Узел UID (
uid_<uid>/memory.min). Устанавливается равным точной сумме управляемых дочерних ограниченийpid_*, поэтому неуправляемые процессы, использующие этот UID (например, другие демоныAID_SYSTEMили пакетыsharedUserId="android.uid.system"), имеютmemory.min = 0и не могут претендовать на защиту. - Фрагмент верхнего уровня (
/sys/fs/cgroup/system/memory.minи/sys/fs/cgroup/apps/memory.min). Значение равно сумме значенийuid_*дочерних фрагментов. Все устаревшие значенияmemory.lowв управляемых узлах сбрасываются до0, а все каталогиuid_*, которые управлялись при предыдущем обновлении (например, предыдущий главный экран после того, как пользователь переключился на другой), сбрасываются до0.
Рисунок 1. Трехуровневое делегирование минимальной защиты между срезами cgroup.
Почему в ядрах MGLRU используется memory.min, а не memory.low
При использовании Multi-Generational LRU (MGLRU, стандартного механизма возврата страниц в ядрах Android 6.6 и более поздних версий) memory.low и memory.min ведут себя в mm/vmscan.c:shrink_one по-разному:
memory.low(отсрочка на один проход). Контрольная группа, использование которой нижеmemory.low, перемещается в конец списка LRU (MEMCG_LRU_TAIL) и пропускается при одном проходе освобождения памяти. При следующем проходе в условиях постоянной нехватки памяти ядро полностью освобождает cgroup и регистрирует событиеMEMCG_LOW. В стресс-тестах физических устройств с анонимным потребителем памяти, занимающим до 85% физической ОЗУ,memory.lowпозволилоSystemUIна умных часах сократиться с ~45 МБ до ~5–10 МБ, аaudioserverна складном устройстве с 16 ГБ памяти – с 15 МБ до 2 МБ при минимальном значении ~14 МБ.memory.min(жесткое ограничение снизу). Контрольная группа, использование которой нижеmemory.min, пропускается при глобальном возврате. Как показано на рисунке 2, при той же рабочей нагрузке с 85% давления на ОЗУmemory.minсбрасывает только 10% самых холодных страниц каждого демона и удерживаетSystemUI,surfaceflingerиaudioserverна уровне 90% без анонимных повторных ошибок и без увеличения количества завершений процессовlmkd.
memory.min обеспечивает физическое резервирование, аналогичное защите, которую дает закрепление PinnerService mlock. Преимущество заключается в том, что LRU-алгоритм ядра (а не статический список файлов) выбирает, какие страницы горячего кода, кучи и графики остаются резидентными.
Рисунок 2. Сохранение рабочего набора демона при растущей нагрузке на память.
Предварительный прогрев при загрузке и освобождение кеша несопоставленных файлов после загрузки
Чтобы заменить постоянный mlock PinnerService в config_defaultPinnerServiceFiles, нужно скоординировать два действия при загрузке:
- Однократный фоновый предварительный запуск при загрузке (
PinnerService): пропуск.PinnerServicemlockбез предварительного запуска кеша страниц переносит чтение с диска файловSystemUIAPK, ODEX и VDEX размером около 95 МБ в синхронный путь загрузки при запускеSystemUI(OnBootPhase_550), что увеличивает время загрузки на 300–450 мс . Вызоваmadvise(MADV_WILLNEED)недостаточно, потому чтоforce_page_cache_raв ядре ограничивает один вызовMADV_WILLNEEDодним окном упреждающего чтения (обычно от 128 КБ до 2 МБ). Вместо этого, когдаSystemMemoryBudgetManager.shouldSkipDefaultSystemPinningактивно,PinnerServiceсопоставляет каждый файл вconfig_defaultPinnerServiceFilesв фоновом потоке во времяonStart, синхронно заполняет файл в кеше страницы с помощьюOs.mlockи немедленно вызываетOs.munlock,munmapиclose. Это позволяет сохранить быструю загрузку и не блокировать байты навсегда, чтобы страницы, загруженные при холодном запуске, можно было удалить позже. - Исключение и возврат кеша сканирования пакетов при загрузке
system_server. Во время загрузкиPackageManagerServiceсканирует установленные APK-файлы, оставляя от 1,5 до 2,5 ГБ холодного несопоставленного кеша страниц файлов, который относится к cgroupsystem_server. Если включить его в расчетmemory.minдляsystem_server, то кеш холодной загрузки заполнит 10% глобального ограничения ОЗУ и пропорционально уменьшит минимальные уровни защиты дляsurfaceflinger,SystemUIиaudioserver. Чтобы избежать нехватки памяти,SystemMemoryBudgetManagerсчитываетmemory.statдляsystem_server, вычисляет кеш несопоставленных файлов какmax(0, file - shmem - file_mapped)и вычитает этот кеш изmemory.currentперед вычислением 90% нижнего предела, как показано на рисунке 3. Кроме того, через 10 секунд послеPHASE_BOOT_COMPLETEDменеджер выполняет однократную запись<unmapped_file> swappiness=0в узелmemory.reclaimsystem_serverна Linux 6.2 и более новых ядрах, чтобы упреждающе вытеснить холодный кеш сканирования пакетов без замены анонимной кучи или вытеснения сопоставленных страниц DEX и кода. В более старых ядрах (например,android14-6.1), которые отклоняютswappiness=0с ошибкойEINVAL, менеджер пропускает записьmemory.reclaim, а не возвращается к неквалифицированной записи возврата, которая заменяет анонимную кучу во время загрузки.
Рисунок 3. Сведение нижнего порога защиты и возврат кеша после загрузки.
Требования к ядру и оверлеи конфигурации устройства
SystemMemoryBudgetManager использует mem_cgroup_calculate_protection в ядре Linux для распространения memory.min по вложенным cgroups. Общие ядра Android android15-6.6 и android16-6.12 по умолчанию поддерживают полную вложенную защиту MGLRU. Для устройств с android14-6.1 и включенным MGLRU требуются три бэкпорта в mm/vmscan.c и mm/memcontrol.c, чтобы пустые промежуточные срезы и UID cgroups распространяли защиту на дочерние процессы cgroups:
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)
OEM-производители могут настраивать или отключать SystemMemoryBudgetManager с помощью следующих оверлеев ресурсов фреймворка:
config_systemMemoryBudgetLowKb(целое число, КБ):0(по умолчанию) ограничивает сумму всех нижних пороговmemory.minна уровне 10% от общего объема физической ОЗУ. Положительное значение задает явное ограничение в КБ, а отрицательное – отключаетSystemMemoryBudgetManagerна устройстве (сохраняя устаревшее статическое закреплениеmlockвPinnerService).config_systemMemoryBudgetHighKb(целое число, КБ):0(по умолчанию) оставляет/sys/fs/cgroup/system/memory.highна уровнеmax. Положительное значение задает верхний предел для верхней частиmemory.high(должно быть больше или равно нижнему пределу).config_systemMemoryBudgetAdditionalSystemProcesses(массив строк) – список дополнительных названий процессов демонов поставщика или системных процессов, критичных к задержкам, в/sys/fs/cgroup/system(сопоставляется с/proc/<pid>/commили базовым именемargv[0]в/proc/<pid>/cmdlineдля системных UID и UID поставщика в10000), которые нужно защитить с помощью 90% минимальных значенийmemory.minнаряду с шестью системными демонами по умолчанию. Используйте этот оверлей вместо настройкиmemory.minвinit.rc, потому чтоSystemMemoryBudgetManagerпересчитываетuid_<uid>/memory.minкак сумму управляемых дочерних процессов и очищает неуправляемые узлыuid_*при каждом обновлении. Например, XR-устройство может защитить свои демоны пространственного отслеживания и HAL камеры с помощью следующего оверлея:<string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses"> <item>android.hardware.perceptionhost</item> <item>auracamerahal</item> </string-array>config_systemUi(строка) – название пакета приложения SystemUI платформы, разрешенное с помощьюMATCH_SYSTEM_ONLY.
Чтобы проверить активные нижние границы SystemMemoryBudgetManager и узлы cgroup на устройстве, выполните следующую команду:
# Inspect policy state, resolved PIDs, and applied floors in dumpsys meminfo 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
Ограничение памяти для постоянно работающих и фоновых системных приложений
Хотя SystemMemoryBudgetManager защищает интерактивные демоны от возврата с помощью порогов memory.min, многие другие системные приложения и привилегированные сервисы платформы долго работают в фоновом режиме как процессы PERSISTENT, связанные активные службы или часто запрашиваемые поставщики контента.
Без явных ограничений по памяти эти процессы не ограничиваются, пока не достигнут лимитов ограничителя памяти в несколько гигабайт (например, от 4 до 10 ГБ для видимых приложений и от 2 до 5 ГБ для фоновых приложений) или пока глобальная нагрузка на память не вызовет активность kswapd и lmkd на уровне системы.
Если не ограничивать фоновые системные процессы, они часто накапливают большие объемы холодной памяти, которая не входит в их активный рабочий набор.
- Неограниченный кеш страниц файлов. Сервисы, которые индексируют медиафайлы, выполняют поиск по базам данных или синхронизируют таблицы календаря и поставщика (например,
AppSearch,MediaProviderиCalendarProvider), сопоставляют или передают большие файлы на диске. Без ограничения cgroupmemory.highчистые страницы, связанные с файлами, из однократных чтений остаются в cgroup процесса неопределенное время, занимая сотни мегабайт физической ОЗУ. - Сохранение анонимных страниц кучи после всплеска активности. Сервисы, в которых наблюдаются кратковременные всплески активности, например
Bluetoothаудио и обнаружение BLE,NetworkStackпроверка TLS на странице входа или переход помощников интерфейса в фоновый режим, могут сохранять холодные анонимные страницы кучи долгое время после всплеска активности.
Применение откалиброванных объявлений <memory-budget> (мягкие ограничения memory.high, обычно от 30 до 190 МБ) к системным приложениям дает два основных преимущества:
- Упреждающее локализованное освобождение страниц. Когда системный процесс превышает заданный лимит
memory.high, ядро Linux направляет освобождение страниц именно в эту группу cgroup, сначала вытесняя холодные страницы, поддерживаемые файлами, и сжимая неактивные анонимные страницы в ZRAM, не дожидаясь глобальной нехватки памяти устройства. - Больше доступной оперативной памяти для активных приложений и пула кешированных приложений. Ограничение кеша холодных файлов в постоянно работающих системных службах освобождает от десятков до сотен мегабайт физической оперативной памяти на устройстве. Это уменьшает задержки в интерфейсе, снижает нагрузку при запуске камеры и позволяет сохранять больше пользовательских приложений в кешированном состоянии.
Настройка лимитов памяти для системных приложений
Для системных приложений используется тот же синтаксис манифеста <memory-budget>, каскадирование состояний процесса, масштабирование плотности экрана и правило разрешения последнего совпадающего условия, что и для обычных приложений. Полный список атрибутов приведен в справочнике по атрибутам XML.
OEM-производители могут объявлять элементы <memory-budget> непосредственно в файле AndroidManifest.xml системного приложения или объединять их из отдельного XML-файла с помощью свойства additional_manifests в Android.bp. По соглашению AOSP хранит бюджеты системных приложений в отдельных файлах *-budget.xml (в frameworks/base/core/memory_budgets/ для пакетов основной платформы или вместе с отдельными модулями, такими как packages/modules/NetworkStack/memory_budgets/, packages/modules/AppSearch/apk/ и packages/services/Telephony/) и прикрепляет их с помощью additional_manifests:
android_app {
name: "TeleService",
manifest: "AndroidManifest.xml",
additional_manifests: ["Telephony-budget.xml"],
// ...
}
Область действия на уровне пакета (UID) и на уровне процесса (PID)
В зависимости от того, какой бюджет вы объявляете, базовый бюджет на уровне пакета (<application>) или бюджет для нескольких процессов (<processes><process>), ядро применяет memory.high на уровне cgroup UID (/sys/fs/cgroup/uid_<uid>/memory.high) или на уровне cgroup PID (/sys/fs/cgroup/uid_<uid>/pid_<pid>/memory.high), как показано на рисунке 4.
- Лимит на уровне пакета (
<application>). Используйте, если APK запускает один процесс под собственным выделенным UID (например,Bluetooth,Nfc,NetworkStack,CalendarProvider,TraceurилиAppSearch). - Бюджет на уровне процесса (
<processes><process>). Используйте, если APK-файл используетandroid:sharedUserId(например,android.uid.phoneс UID1001илиandroid.uid.systemс UID1000) совместно с другими системными пакетами, чтобы другие процессы, использующие этот UID, не ограничивались вместе, или когда один APK-файл запускает несколько процессов с разными ролями памяти (например, фоновый демонMediaProviderи подпроцесс пользовательского интерфейса:PhotoPicker).
Рисунок 4. Ограничения памяти на уровне UID и PID.
В примере ниже показан параметр MediaProvider-budget.xml, который ограничивает бюджеты, зависящие от ОЗУ, процессом MediaProvider по умолчанию (<process>), а бюджет, зависящий от масштаба экрана, – процессом пользовательского интерфейса :PhotoPicker. Каждый атрибут android:maxDeviceRamMb соответствует устройствам, у которых общий объем физической ОЗУ (MemTotal) не превышает указанное пороговое значение в МБ. Если атрибут не задан, он применяется к устройствам с любым объемом ОЗУ. Условия упорядочены от значения по умолчанию без ограничений до наименьшего порогового значения maxDeviceRamMb, чтобы правило разрешения последнего совпадающего условия выбирало наиболее строгий применимый уровень:
<?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>
Как калибровались эталонные бюджеты AOSP на устройствах Google Pixel
Лимиты памяти для системных приложений в AOSP были откалиброваны на устройствах Google Pixel с объемом физической ОЗУ от 6 до 16 ГБ (включая телефоны, складные устройства и планшеты с 6, 8, 12 и 16 ГБ ОЗУ) и разрешением экрана от ~1, 2 Мп (720 × 1600) до ~4, 5 Мп (2076 × 2152).
Калибровка объединила два дополнительных источника данных: телеметрию полей cgroup v2 для всего парка и контролируемые проверки физических устройств memory.high по критическим пользовательским путям (CUJ).
Анализ телеметрии cgroup v2 для всего парка устройств
Периодические снимки cgroup v2 собирались на всех устройствах и разбивались по объему физической ОЗУ (≤8 ГБ, 12 ГБ и ≥16 GB) и сегменту состояния процесса (foreground, perceptible, background и cached). Статистический анализ проводился на основе шести принципов:
- Разделение
memory.currentна рабочий набор и освобождаемый кеш файлов. Посколькуmemory.highуправляет резидентной нагрузкой cgroup (memory.current), каждый снимок был разделен с помощьюmemory.statна анонимную кучу (anon), анонимные страницы подкачки (swap) и кеш страниц на основе файлов (file), как показано на рисунке 5. Общий несжимаемый анонимный спрос (anon + swapпри P99) устанавливает минимальный рабочий набор для бюджета, чтобы процесс не рисковал исчерпанием ZRAM, в то время как большой разрыв междуmemory.currentP99 иanon + swapP99 указывает на кэш страниц файлов, которыйmemory.highможет восстановить, при условии, что кэш не сопоставлен и не используется. - Учитывайте память, которую
memory.highне может освободить. Не все файлы и общая память – это холодный кеш. Заблокированные страницы (unevictableвmemory.stat, например общая память, заблокированная с помощьюmlock, или заблокированная гостевая ОЗУ виртуальной машины, хост-процессы которой относятся к cgroup приложения) устанавливают жесткий нижний предел бюджета, а сопоставленные страницы (file_mapped) часто являются частью активного рабочего набора. Прежде чем считать память, занятую файлами, доступной для повторного использования, проверьтеunevictable,shmemиfile_mapped. Например, около 100 МБ памятиAppSearchзаблокировано и не может быть освобождено, поэтому бюджет покрывает этот минимум плюс рабочий набор горячего хоста с запасом для роста заблокированной области. - Отфильтруйте данные, полученные в режиме ожидания. Поставщики контента (
CalendarProvider) и инструменты разработчика, работающие по запросу (Traceur), от 70 до 99,9 % времени находятся в режиме ожидания, потребляя от 1 до 3 МБ памяти. Вычисление необработанных процентилей по всем снимкам сильно размывает оценки P95 и P99. Вместо этого процентили и доверительные интервалы рассчитывались по неразбавленным активным снимкам (с фильтрацией снимковCACHEDи снимков базового уровня бездействия). - Используйте доверительные интервалы, сгруппированные по устройствам. Поскольку одно устройство со временем предоставляет множество периодических снимков, обработка снимков как независимых выборок занижает неопределенность хвоста. Все оценки рабочих наборов P95 и P99 были рассчитаны с использованием консервативных 95 % доверительных интервалов, сгруппированных по устройствам (в качестве эффективного размера выборки использовалось количество отдельных устройств
N_dev). - Учитывайте кратковременные всплески с помощью
memory.peak. Периодическая выборка может не учитывать кратковременные всплески с высоким уровнем выделения памяти, напримерNetworkStackпроверку TLS на странице входа и одновременные DNS-пробы илиBluetoothинициализацию A2DP и BLE. Для сервисов с пиковыми нагрузками бюджеты были рассчитаны так, чтобы покрывать от 90 до 95 % максимального объема памятиmemory.peak, используемой ядром за все время работы, на разных уровнях ОЗУ. - Убедитесь, что уровень ОЗУ отражает объем ОЗУ, а не поколение устройства. Новые устройства обычно имеют больше ОЗУ и больше функций, поэтому рабочий набор, который растет вместе с уровнем ОЗУ, на самом деле может расти вместе с функциями. Прежде чем добавлять уровень
maxDeviceRamMb, сегментируйте данные по моделям устройств. Например,com.android.phoneдостигает того же P99anon + swap(примерно 85–90 МБ) на телефонах с 12 и 16 ГБ ОЗУ одного поколения, а на более старом складном телефоне с 12 ГБ ОЗУ использует 40 МБ. Поэтому в бюджете используется одно значение без уровня ОЗУ, чтобы устройство с меньшим объемом ОЗУ, которое поставляется с новыми функциями телефонии, не было недофинансировано.
Рисунок 5. Рабочий набор и кеш файлов, доступных для освобождения.
Проверка критических путей пользователя и масштабирования экрана на физических устройствах
Чтобы проверить бюджеты, полученные на основе телеметрии, и откалибровать бюджеты, пропорциональные дисплею (например, :PhotoPicker), и бюджеты для фонового режима в зависимости от состояния (например, Traceur), на физических устройствах с ОЗУ от 6 до 16 ГБ были выполнены проверки memory.high.
- Поиск точки, в которой повторные ошибки отсутствуют. При выполнении сквозных пользовательских сценариев (таких как быстрая прокрутка сетки фотографий, просмотр альбома, пролистывание полноэкранных предварительных версий фотографий и видео в высоком разрешении, а также запись и сохранение трассировки) значение
memory.highуменьшалось, а счетчики cgroupmemory.stat(workingset_refault_anon,workingset_refault_file,pgscanиpgsteal) иmemory.events(high) измерялись. Как показано на рисунке 6, оптимальный бюджет находится чуть выше точки, в которой повторные ошибки отсутствуют. Точка перегиба – это пороговое значение, при котором ядро освобождает холодный одноразовый файловый кеш (pgsteal > 0), не вызывая пробуксовки активного рабочего набора (workingset_refault == 0). - Проверка масштабирования пикселей экрана при разных разрешениях. Тестирование
:PhotoPickerна четырех физических и эмулированных разрешениях экрана от ~1,2 Мпкс (720 × 1600) до ~4,5 Мпкс (2076 × 2152, развернутый внутренний экран) подтвердило, что активный рабочий набор битовых карт Glide и ViewPager2 масштабируется линейно с количеством пикселей экрана. Базовый объем памяти, не связанный с экраном, также зависит от устройства. При почти одинаковом количестве пикселей точка перегиба без перезагрузок составила около 140 МБ на внешнем экране складного устройства с 16 ГБ памяти и 110–125 МБ на телефоне с 6 ГБ памяти. Базовый объем 85 МБ плюс 25 байт на пиксель экрана (android:maxMb="85"иandroid:additionalBytesPerDisplayPixel="25") превышает измеренное пороговое значение на каждом протестированном экране (около 110 МБ на HD+, около 145 МБ на FHD+ и около 190 МБ на раскладных экранах). - Проверка сборки мусора ART в фоновом режиме. В cgroup v2 общие страницы
boot.artZygote и страницы изображений фреймворка остаются в cgroup Zygote и обрабатываются как иммунное пространство во время параллельной сборки мусора ART с пометкой и сжатием (CMC). Стресс-тестирование подтвердило, что когда приложение системного интерфейса переходит в состояниеbackground, ограничениеmemory.high(например, до 30 МБ наTraceur) четко ограничивает частную кучу и кэш страниц, не вызывая сбоев сборки мусора или активности подкачки.
Рисунок 6. Поиск точки перегиба с нулевым количеством повторных сбоев во время CUJ.
Сводка по бюджетам системных приложений в эталонной версии AOSP
В таблице ниже приведены калиброванные контрольные бюджеты в AOSP для разных уровней ОЗУ и состояний процессов.
| Системный пакет или процесс | Область действия | ≤8 ГБ ОЗУ | 12 ГБ ОЗУ | ≥16 ГБ ОЗУ | Основной драйвер калибровки |
|---|---|---|---|---|---|
com.android.bluetooth |
UID (<application>) |
80 МБ | 95 МБ | 110 МБ | Охватывает 90 % времени работы устройства, memory.peak пакетов (~75–110 МБ) и более чем в два раза больше активных memory.current, чем 99 % времени работы устройства. |
com.android.nfc |
UID (<application>) |
50 МБ | 60 МБ | 60 МБ | Охватывает активную технологию Host Card Emulation P99 (до ~50 МБ) и P90–P95 memory.peak при очистке холодного кеша файлов. |
com.android.providers.calendar |
UID (<application>) |
70 МБ по умолчанию (активный режим) 50 МБ perceptible и background |
Активный рабочий набор не зависит от объема ОЗУ. Бюджет фонового режима сохраняет анонимный запас для напоминаний, сокращая кеш страниц SQLite во время синхронизации. | ||
com.android.traceur |
UID (<application>) |
85 МБ по умолчанию (активные и заметные) 30 МБ background |
Сохраняет полный запас в 85 МБ во время активной записи и экспорта трассировки (TraceService), ограничивая состояние бездействия и трансляции до 30 МБ. |
||
com.android.providers.media.module (основной) |
PID (<process>) |
60 МБ | 75 МБ | 85 МБ | Освобождает около 10–30 МБ сверх 99-го процентиля anon + swap, очищая кеш неиспользуемых файлов SQLite и значков. |
com.android.providers.media.module:PhotoPicker |
PID (<process>) |
85 МБ + 25 байт на пиксель (~110 МБ на HD+, ~145 МБ на FHD+, ~190 МБ на складном экране с разрешением ~4,5 Мп) |
Масштабируется пропорционально количеству пикселей экрана (additionalBytesPerDisplayPixel="25") в соответствии с точкой перегиба рабочего набора битовых карт Glide. |
||
com.android.networkstack |
UID (<application>) |
65 МБ | 70 МБ | 85 МБ | Преимущественно анонимная память, размер которой соответствует 95-му процентилю времени жизни memory.peak, чтобы покрывать всплески трафика TLS, DNS и DHCP на странице входа. |
com.android.appsearch.apk |
UID (<application>) |
130 МБ | Охватывает около 100 МБ заблокированной памяти, которую нельзя освободить, и около 5 МБ рабочего набора хоста, а также предусматривает возможность увеличения заблокированной области. При освобождении кеша холодных страниц (до ~330 МБ при P99) не было зафиксировано повторных ошибок при ≥125 МБ. | ||
com.android.phone (TeleService) |
PID (<process>) |
120 МБ | Область действия ограничена PID на общем UID 1001. Рабочий набор отслеживает поколение устройства, а не ОЗУ, поэтому один бюджет примерно на 30 МБ выше самого высокого значения P99 anon + swap для каждой модели. |
||
Как производители оригинального оборудования могут перенастроить лимиты для системных приложений на своих устройствах
Производители оригинального оборудования, которые настраивают системные приложения (например, добавляют профили Bluetooth поставщика, пользовательские расширения телефонии или индексаторы мультимедиа) или поставляют дополнительные предустановленные системные сервисы, могут перенастроить существующие бюджеты или создать новые файлы *-budget.xml для своего аппаратного портфолио.
Сбор активных профилей памяти cgroup v2
Для каждого целевого системного процесса прочитайте узлы памяти cgroup v2 в /sys/fs/cgroup/uid_<uid>/pid_<pid>/ для репрезентативных рабочих нагрузок и уровней ОЗУ:
memory.current– текущая резидентная память, выделенная для cgroup (за исключением страниц, общих с Zygote).memory.peak: максимальное значениеmemory.currentза все время с момента запуска процессаmemory.swap.current– текущий объем сжатой памяти подкачки в ZRAM.memory.stat: разбивка поanon,file,pgscan,pgsteal,workingset_refault_anonиworkingset_refault_filememory.events– общее количество нарушенийhigh(memory.highсобытий ограничения и возврата).
При агрегировании телеметрии с устройств, используемых для тестирования или в полевых условиях, отделяйте состояния активных процессов (TOP, FOREGROUND_SERVICE, активные привязки и активные синхронизации) от состояний бездействия CACHED или PERSISTENT. Это позволит избежать искажения расчетов рабочих наборов P95 и P99 в сторону уменьшения.
Отделение несжимаемой кучи от восстанавливаемого кеша файлов
Сравните 99-й процентиль anon + swap с 99-м процентилем memory.current:
- Сервисы, в которых преобладает кеш файлов (
file≫anon, например поставщики баз данных, индексаторы поиска и демоны журналов). Установитеandroid:maxMb, чтобы обеспечить комфортный запас (обычно от 1,5 до 4 раз) по сравнению с P99anon + swap, а также рабочее окно для страниц горячих файлов. Эта конфигурация позволяет ядру непрерывно вытеснять холодные несопоставленные страницы файлов, не затрагивая анонимную кучу. Еслиunevictableилиfile_mappedимеет большой размер (как в случае с заблокированной памятью или базами данных, сопоставленными с памятью), учитывайте этот объем как рабочий набор. - Сервисы, в которых преобладает анонимная куча или наблюдаются всплески (
anonсоставляет большую частьmemory.current, например стеки сети или подключения). Размерandroid:maxMbдолжен покрывать от 90 до 95 %memory.peak, чтобы временные всплески не запускали синхронную выгрузку ZRAM в потоках, чувствительных к задержке. - Процессы пользовательского интерфейса, интенсивно использующие экран (например, средства выбора, галереи или системные помощники пользовательского интерфейса). Измерьте базовый объем памяти, не связанный с пользовательским интерфейсом, и дополнительный объем памяти, занимаемый растровыми изображениями, на мегапиксель на экранах с самым низким и самым высоким разрешением и выразите бюджет с помощью
android:maxMbплюсandroid:additionalBytesPerDisplayPixel.
Очистка memory.high на физических устройствах
Прежде чем выделять новый бюджет на сборку, используйте am memory-limiter manual (или запишите непосредственно в узел cgroup memory.high процесса в сборке userdebug), чтобы протестировать возможные ограничения при выполнении автоматических тестов CUJ:
# Override memory.high for a specific PID to 60 MB during a CUJ test
adb shell am memory-limiter manual <PID> 60MBОтслеживайте memory.stat и memory.events до и после CUJ:
- Бюджет слишком мал.
workingset_refault_anonилиworkingset_refault_fileрезко возрастает во время CUJ,memory.swap.currentнепрерывно растет во время активной работы в активном режиме или задержка кадра CUJ или Binder регрессирует. Увеличьте бюджет, чтобы он превышал значение по умолчанию. - Бюджет слишком велик. Значение
memory.events:highостается равным0даже после интенсивной потоковой передачи файлов или индексирования, а значениеfileвmemory.statнеограниченно растет за счет неиспользуемых страниц. Ограничьте бюджет ещё больше. - Бюджет хорошо откалиброван. В нормальном режиме работы
memory.currentне превышаетmemory.high. При резком увеличении объема файлов или кешаpgstealвосстанавливает страницы неиспользуемых файлов, в то время какworkingset_refault_anonостается близким к нулю, а задержка CUJ не меняется.