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

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

Механизм

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

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

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

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

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

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

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

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

По умолчанию Memory Limiter отслеживает процессы приложений (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, Memory Limiter обнаруживает это событие и может запустить диагностические действия, например записать профиль памяти или зарегистрировать аномалию в 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, доступную операционной системе после вычета памяти, выделенной для аппаратного и встроенного ПО. Выделенные области резервируют ОЗУ для специализированных аппаратных компонентов, таких как графический процессор, модем основной полосы частот, процессор обработки изображений камеры, доверенная среда выполнения и гипервизоры. Обычно они занимают от 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

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

swapNotVisible

Ограничение на размер swap-пространства (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 ГиБ)

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

Конфигурация Memory Limiter разработана на основе следующих принципов платформы:

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

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

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

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

  • Освобождение памяти ядром и динамика подкачки. Когда процесс приложения приближается к лимиту memory.high, ядро Linux направляет освобождение памяти именно в группу управления этого процесса. В частности, удаляются неактивные страницы, поддерживаемые файлами, и переносятся в 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

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

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