В 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
Переопределения, заданные вручную, действуют только в течение времени существования конкретного экземпляра процесса. Если процесс перезапускается, он возвращается к стандартным ограничениям в зависимости от своего состояния.
Ручная корректировка не может превышать физические ограничения системы.