Ограничитель памяти

В Android 17 и более поздних версиях есть Ограничитель памяти – системный сервис, который отслеживает и ограничивает использование памяти процессами приложений с помощью Linux cgroup v2. Ограничитель памяти не позволяет отдельным приложениям потреблять непропорционально большой объем системной памяти, что снижает общую нагрузку на память и предотвращает агрессивное завершение работы критически важных процессов переднего плана и кэшированных процессов из-за нехватки памяти.

Механизм

Ограничитель памяти интегрирован с сервисом Activity Manager Service (AMS), чтобы отслеживать события жизненного цикла процессов и изменения состояния. Ограничитель памяти обеспечивает соблюдение лимитов памяти с помощью иерархии cgroup v2 ядра Linux.

Чтобы поддерживать Memory Limiter, ядро устройства должно включать cgroup v2 и контроллер memory. Сервис использует следующие атрибуты:

memory.high
Регулируемое ограничение. Если лимит превышен, процесс замедляется и ядро пытается освободить страницы, используемые этим процессом.
memory.swap.max
Ограничивает максимальный объем пространства подкачки (например, ZRAM), который может использовать процесс.

Влияние на приложения

Ограничитель памяти не влияет на приложения, которые не превышают лимиты.

Когда приложение превышает лимит memory.high, ядро вытесняет из памяти, поддерживаемой файлами, чистые данные приложения и переносит неактивные анонимные данные в файл подкачки, чтобы приложение не выходило за рамки лимита. В результате вытеснения страниц и обмена данными приложение может временно работать медленнее, но оно продолжает выполняться.

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

Мониторинг процессов

По умолчанию ограничитель памяти отслеживает процессы приложений (UID >= 10000). Исключение составляют основные системные процессы, необходимые для стабильной работы устройства.

Ограничитель памяти назначает лимиты на основе состояния видимости процесса:

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

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

В таблице ниже показано, как состояния процессов соотносятся с классификацией ограничений памяти.

Состояние процессаЛимит памяти
PERSISTENTБез ограничений
PERSISTENT_UIБез ограничений
TOPВидимость
BOUND_TOPВидимость
FOREGROUND_SERVICEНе отображается
BOUND_FOREGROUND_SERVICEНе отображается
IMPORTANT_FOREGROUNDВидимость
IMPORTANT_BACKGROUNDНе отображается
TRANSIENT_BACKGROUNDНе отображается
BACKUPНе отображается
SERVICEНе отображается
RECEIVERНе отображается
TOP_SLEEPINGВидимость
HEAVY_WEIGHTНе отображается
HOMEНе отображается
LAST_ACTIVITYНе отображается
CACHED_ACTIVITYКешировано
CACHED_ACTIVITY_CLIENTКешировано
CACHED_RECENTКешировано
CACHED_EMPTYКешировано

В кешированном состоянии процессы замораживаются, а затем максимально освобождаются.

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

Конфигурация

В Android 17 и более поздних версиях ограничитель памяти настраивается с помощью стандартного файла конфигурации платформы в системном разделе по пути /system/etc/memory-limiter-config.xml.

Формат XML

Файл конфигурации соответствует схеме, определенной в memory-limiter-config.xsd. В файле определено несколько наборов ограничений, упорядоченных по объему доступной памяти. Сервис выбирает набор с наибольшим объемом, который соответствует доступной оперативной памяти устройства. Все значения памяти указаны в мебибайтах (МиБ).

<MemoryLimiterConfig>
  <version>1</version>
  <configList>
    <!-- RAM boundaries account for MemTotal excluding carve-outs. -->
    <limitSet>
      <!-- Limits for 16GB RAM device [14336, 18432): 10G/5G/5G/5G -->
      <minimumRequiredMemTotal>14336</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>18432</maximumAllowedMemTotal>
      <memVisible>10240</memVisible>
      <memNotVisible>5120</memNotVisible>
      <swapVisible>5120</swapVisible>
      <swapNotVisible>5120</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 12GB RAM device [9216, 14336): 8G/4G/4G/4G -->
      <minimumRequiredMemTotal>9216</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>14336</maximumAllowedMemTotal>
      <memVisible>8192</memVisible>
      <memNotVisible>4096</memNotVisible>
      <swapVisible>4096</swapVisible>
      <swapNotVisible>4096</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 8GB RAM device [6800, 9216): 5G/3G/3G/3G -->
      <minimumRequiredMemTotal>6800</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>9216</maximumAllowedMemTotal>
      <memVisible>5120</memVisible>
      <memNotVisible>3072</memNotVisible>
      <swapVisible>3072</swapVisible>
      <swapNotVisible>3072</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 6GB RAM device [4800, 6800): 4G/2G/2G/2G -->
      <minimumRequiredMemTotal>4800</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>6800</maximumAllowedMemTotal>
      <memVisible>4096</memVisible>
      <memNotVisible>2048</memNotVisible>
      <swapVisible>2048</swapVisible>
      <swapNotVisible>2048</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 4GB RAM device [3200, 4800): 2G/1G/1G/1G -->
      <minimumRequiredMemTotal>3200</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>4800</maximumAllowedMemTotal>
      <memVisible>2048</memVisible>
      <memNotVisible>1024</memNotVisible>
      <swapVisible>1024</swapVisible>
      <swapNotVisible>1024</swapNotVisible>
    </limitSet>
  </configList>
</MemoryLimiterConfig>
version
Положительное целое число, обозначающее версию конфигурации. Это 1.
minimumRequiredMemTotal

Минимальный объем системной MemTotal (в МиБ, включая нижнюю границу), необходимый для применения этого набора ограничений. Сервис сравнивает это значение с общим объемом доступной памяти, который сообщает ядро в /proc/meminfo.

Понимание выделения памяти и границ. Значение ядра MemTotal отражает физическую память DRAM, доступную операционной системе после вычета памяти, выделенной для аппаратного и встроенного ПО. Выделенные области резервируют ОЗУ для специализированных аппаратных компонентов, таких как графический процессор, модем базовой полосы частот, ISP камеры, безопасные среды выполнения и гипервизоры. Обычно они занимают от 500 МБ до 2 ГБ физической памяти.

Поскольку MemTotal меньше, чем заявленный объем физической памяти DRAM, пороговые значения minimumRequiredMemTotal включают запас для этих вычетов. Например, для конфигурации, предназначенной для устройств с 16 ГБ памяти, указывается минимальное значение 14336 (14 ГиБ), а не 16384 (16 ГиБ), а для устройств с 12 ГБ памяти – 9216 (9 ГиБ). Это гарантирует, что каждое устройство будет соответствовать своему уровню независимо от вариаций на уровне платы.

maximumAllowedMemTotal

Максимальный объем системной памяти MemTotal (в МиБ, исключая верхний предел), при котором применяется этот набор ограничений. Если значение MemTotal больше или равно этому потолку, установленное ограничение не применяется. Для устройств с объемом памяти 18 432 МиБ и более или менее 3200 МиБ ограничитель памяти не применяется, и процессы не ограничиваются.

memVisible

Мягкое ограничение памяти (memory.high) в МиБ, применяемое к видимым процессам.

memNotVisible

Мягкое ограничение памяти (memory.high) в МиБ, применяемое к невидимым процессам.

swapVisible

Ограничение на размер раздела подкачки (memory.swap.max) в МБ, применяемое к видимым процессам.

swapNotVisible

Ограничение на размер раздела подкачки (memory.swap.max) в МиБ, применяемое к невидимым процессам.

Стандартные ограничения платформы

В Android 17 и более поздних версиях платформа задает стандартные лимиты памяти и файла подкачки в файле /system/etc/memory-limiter-config.xml для распространенных уровней физической ОЗУ, используя интервалы диапазона [floor, ceiling):

Целевая физическая ОЗУ Диапазон MemTotal [нижний предел, верхний предел)
(minimumRequiredMemTotal,
maximumAllowedMemTotal)
Видимый лимит памяти
(memVisible)
Лимит памяти для невидимых объявлений
(memNotVisible)
Ограничение видимых замен
(swapVisible)
Невидимый лимит обмена
(swapNotVisible)
16 ГБ [14336, 18432) МиБ 10 240 МиБ (10 ГиБ) 5120 МиБ (5 ГиБ) 5120 МиБ (5 ГиБ) 5120 МиБ (5 ГиБ)
12 ГБ [9216, 14336) МиБ 8192 МиБ (8 ГиБ) 4096 МиБ (4 ГиБ) 4096 МиБ (4 ГиБ) 4096 МиБ (4 ГиБ)
8 ГБ [6800, 9216) МиБ 5120 МиБ (5 ГиБ) 3072 МиБ (3 ГиБ) 3072 МиБ (3 ГиБ) 3072 МиБ (3 ГиБ)
6 ГБ [4800, 6800) МиБ 4096 МиБ (4 ГиБ) 2048 МиБ (2 ГиБ) 2048 МиБ (2 ГиБ) 2048 МиБ (2 ГиБ)
4 ГБ [3200, 4800) МиБ 2048 МиБ (2 ГиБ) 1024 МиБ (1 ГиБ) 1024 МиБ (1 ГиБ) 1024 МиБ (1 ГиБ)

Принципы ограничения памяти

Конфигурация ограничителя памяти основана на следующих принципах:

  • Согласованность экосистемы и совместимость приложений. Единые ограничения памяти на разных устройствах обеспечивают предсказуемую производительность приложений в экосистеме Android. Приложения разрабатываются и тестируются с учетом стандартных требований к памяти. Единые ограничения платформы позволяют избежать неожиданного снижения производительности или преждевременного завершения работы.

  • Пропорциональное распределение ресурсов. Лимиты платформы калибруются в соответствии с емкостью физической ОЗУ:

    • Видимые процессы. Выделено примерно от 1/2 до 2/3 от общего объема физической ОЗУ для поддержки активного пользовательского интерфейса, композиции и рабочих нагрузок отрисовки.
    • Невидимые процессы. Для фоновых задач выделяется примерно от 1/4 до 1/3 от общего объема физической ОЗУ.
  • Универсальность. Ограничения применяются ко всем процессам приложений на устройстве (UID >= 10000), включая предустановленные системные приложения и приложения от OEM-производителей. Ограничитель памяти не поддерживает белые списки для исключения определенных приложений, обеспечивая справедливое управление памятью для всех приложений.

  • Отсутствие API для запросов во время выполнения. В Android 17 и более поздних версиях приложения не могут программно запрашивать назначенные им ограничения памяти во время выполнения. Стандартные ограничения платформы установлены с большим запасом, чтобы приложения, работающие корректно, не испытывали ограничений при обычном использовании.

  • Динамика возврата памяти ядром и обмена. Когда процесс приложения приближается к лимиту memory.high, ядро Linux направляет возврат памяти именно в cgroup этого процесса. Это включает удаление неактивных страниц, поддерживаемых файлами, и перенос холодных анонимных страниц в ZRAM, что позволяет поддерживать плавную работу системы без создания глобальной нагрузки на память.

Команды оболочки

Команда am memory-limiter позволяет разработчикам и системным интеграторам взаимодействовать с сервисом во время выполнения для разработки, тестирования и отладки:

am memory-limiter <SUB-COMMAND>

статус

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

adb shell am memory-limiter status

Пример выходных данных:

Memory limiter
  enabled                  monitoring=true          ignored=none
  visibleMem=1948MB        visibleSwap=974MB
  notVisibleMem=974MB      notVisibleSwap=487MB
  started=36               watched=36               watch-failed=0
  events=0                 processes=36             process-hwm=36

Основные поля в выходных данных:

monitoring
Указывает, отслеживает ли Ограничитель памяти процессы.
visibleMem и notVisibleMem
Рассчитанные абсолютные ограничения памяти (memory.high), действующие в настоящее время для каждого состояния видимости.
visibleSwap и notVisibleSwap
Рассчитанные абсолютные лимиты на обмен (memory.swap.max), которые в настоящее время применяются для каждого состояния видимости.
events
Количество случаев, когда процесс превысил назначенный лимит.
processes
Текущее количество отслеживаемых процессов.

игнорировать

Подчиненная команда ignore временно исключает определенный UID или все процессы из ограничения памяти. Это полезно для оценки производительности, стресс-тестирования или диагностики поведения памяти:

# Ignore a specific UID
adb shell am memory-limiter ignore 10087
# Ignore all processes (temporarily disables limiting)
adb shell am memory-limiter ignore all
# Resume normal limiting operation
adb shell am memory-limiter ignore none

вручную

Субкоманда manual переопределяет рассчитанные ограничения для определенного процесса по идентификатору процесса (PID) с помощью настраиваемого абсолютного значения в байтах. Значение должно быть целым числом, но может включать суффикс MB для указания МиБ или суффикс GB для указания ГиБ:

# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1073741824
# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1024MB
# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1GB
# Remove the manual override for PID 1234
adb shell am memory-limiter manual 1234 none

Переопределения, заданные вручную, действуют только в течение времени выполнения определенного экземпляра процесса. Если процесс перезапустится, будут восстановлены стандартные ограничения в зависимости от состояния ресурса.

Заданное вручную значение не может превышать физические ограничения системы.