Limitador de memoria

Android 17 y versiones posteriores incluyen el Limitador de memoria, un servicio del sistema que supervisa y limita el uso de memoria de los procesos de las aplicaciones con cgroup v2 de Linux. El limitador de memoria evita que las apps individuales consuman memoria del sistema de forma desproporcionada, lo que reduce la presión general de la memoria y evita que se cierren de forma agresiva los procesos críticos en primer plano y en caché por falta de memoria.

Mecanismos

El limitador de memoria se integra en el servicio de administrador de actividades (AMS) para hacer un seguimiento de los eventos del ciclo de vida de los procesos y los cambios de estado. El limitador de memoria aplica límites de memoria con la jerarquía de cgroup v2 del kernel de Linux.

Para admitir el limitador de memoria, el kernel del dispositivo debe habilitar cgroup v2 y el controlador memory. El servicio se basa específicamente en los siguientes atributos:

memory.high
Un límite flexible. Cuando se supera, el proceso se limita y el kernel intenta recuperar páginas de forma proactiva de ese proceso.
memory.swap.max
Limita la cantidad máxima de espacio de intercambio (como ZRAM) que puede usar el proceso.

Impacto en las apps

Las apps que operan dentro de sus límites de memoria no se ven afectadas por el limitador de memoria.

Cuando una app supera su límite de memory.high, el kernel expulsa la memoria limpia respaldada por archivos de la app y cambia su memoria anónima inactiva para mantener la app dentro del límite. Como resultado de esta actividad de expulsión de páginas y de intercambio, la app puede experimentar una limitación temporal de la ejecución, pero sigue ejecutándose.

En situaciones extremas, como una fuga de memoria descontrolada en la que la app sigue asignando memoria anónima después de que se agota el espacio de intercambio, la app no puede asignar memoria y se finaliza.

Supervisión de procesos

El limitador de memoria aplica los límites de la plataforma de esta página a los procesos de la app (UID >= 10000). Los procesos con un UID inferior a 10000 no reciben límites de la plataforma, como tampoco el paquete del servicio de inferencia de zona de pruebas integrado en el dispositivo predeterminado (config_defaultOnDeviceSandboxedInferenceService). Las apps y los servicios del sistema pueden declarar presupuestos de memoria más estrictos, como se describe en Presupuestos de memoria del sistema.

El limitador de memoria asigna límites de memoria según el estado de visibilidad del proceso:

  • Procesos visibles: Son procesos que alojan una IU visible para el usuario (como la actividad enfocada actualmente o una interacción activa). Debido a que mostrar una IU requiere un conjunto de trabajo más grande para las canalizaciones de renderización y gráficos, los procesos visibles reciben un límite de memoria más generoso.

  • Procesos no visibles: Son procesos que realizan tareas en segundo plano sin presentar una IU (como servicios en segundo plano o receptores de transmisiones). Como no renderizan componentes de la IU, operan con un límite más restrictivo.

En la siguiente tabla, se asignan estados de proceso específicos a las clasificaciones de límites de memoria de la plataforma:

Estado del procesoLímite de memoria
PERSISTENTSin restricciones
PERSISTENT_UISin restricciones
TOPVisible
BOUND_TOPVisible
FOREGROUND_SERVICENo visible
BOUND_FOREGROUND_SERVICENo visible
IMPORTANT_FOREGROUNDVisible
IMPORTANT_BACKGROUNDNo visible
TRANSIENT_BACKGROUNDNo visible
BACKUPNo visible
SERVICENo visible
RECEIVERNo visible
TOP_SLEEPINGVisible
HEAVY_WEIGHTNo visible
HOMENo visible
LAST_ACTIVITYNo visible
CACHED_ACTIVITYAlmacenada en caché
CACHED_ACTIVITY_CLIENTAlmacenada en caché
CACHED_RECENTAlmacenada en caché
CACHED_EMPTYAlmacenada en caché

Los procesos PERSISTENT y PERSISTENT_UI no reciben un límite de plataforma. Si uno de estos procesos declara un presupuesto de memoria, se aplica su presupuesto en primer plano, como se describe en Buckets de presupuesto de memoria.

En el estado almacenado en caché, los procesos se congelan y, luego, se recuperan al máximo.

Cuando un proceso supera el límite de memory.high asignado, el limitador de memoria detecta el evento y puede activar acciones de diagnóstico, como capturar un generador de perfiles de memoria o registrar una anomalía en statsd.

Buckets de presupuesto de memoria

En los dispositivos que admiten presupuestos de memoria, las apps y los servicios del sistema pueden declarar presupuestos más estrictos que los límites de la plataforma. Las apps declaran presupuestos en su manifiesto, como se describe en Cómo establecer presupuestos de memoria de la app. Para las apps y los servicios del sistema, consulta Presupuestos de memoria del sistema. Un presupuesto establece memory.high y no puede superar el límite de la plataforma para el mismo estado del proceso.

Cuando un proceso tiene un presupuesto, el limitador de memoria asigna su estado a uno de cuatro buckets. Los valores foreground, perceptible y background de android:state del manifiesto seleccionan el presupuesto para el bucket coincidente. Los procesos en el bucket almacenado en caché no tienen un presupuesto activo.

Estado del procesoIntervalo de presupuesto
PERSISTENTPrimer plano
PERSISTENT_UIPrimer plano
TOPPrimer plano
BOUND_TOPPrimer plano
TOP_SLEEPINGPrimer plano
FOREGROUND_SERVICEPerceptible
BOUND_FOREGROUND_SERVICEPerceptible
IMPORTANT_FOREGROUNDPerceptible
IMPORTANT_BACKGROUNDPerceptible
TRANSIENT_BACKGROUNDFondo
BACKUPFondo
SERVICEFondo
RECEIVERFondo
HEAVY_WEIGHTFondo
HOMEAlmacenada en caché
LAST_ACTIVITYAlmacenada en caché
CACHED_ACTIVITYAlmacenada en caché
CACHED_ACTIVITY_CLIENTAlmacenada en caché
CACHED_RECENTAlmacenada en caché
CACHED_EMPTYAlmacenada en caché

Configuración

En Android 17 y versiones posteriores, el Limitador de memoria se configura con el archivo de configuración de la plataforma estándar en la partición del sistema en /system/etc/memory-limiter-config.xml.

Formato XML

El archivo de configuración sigue el esquema definido en memory-limiter-config.xsd. El archivo define varios conjuntos de límites ordenados por memoria disponible. El servicio selecciona el conjunto de límites más alto que coincida según la RAM disponible del dispositivo. Todos los valores de memoria se especifican en mebibytes (MiB).

<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
Es un número entero positivo que identifica la versión de la configuración. Debe ser 1.
minimumRequiredMemTotal

Es el valor mínimo de MemTotal del sistema (en MiB, límite inferior inclusivo) que se requiere para que se aplique este conjunto de límites. El servicio evalúa esto en función de la memoria total disponible que informa el kernel en /proc/meminfo.

Comprende los límites y las asignaciones de memoria: El valor MemTotal del kernel refleja la DRAM física a la que puede acceder el sistema operativo después de deducir las asignaciones de memoria de hardware y firmware. Las reservas de memoria asignan RAM para componentes de hardware especializados, como la GPU, el módem de banda base, el ISP de la cámara, los entornos de ejecución seguros y los hipervisores, lo que suele consumir entre 500 MiB y más de 2 GiB de memoria física.

Debido a que MemTotal es inferior a la DRAM física anunciada del dispositivo, los umbrales de minimumRequiredMemTotal incluyen un margen para estas exclusiones. Por ejemplo, una configuración orientada a dispositivos de 16 GB especifica un límite inferior de 14336 (14 GiB) en lugar de 16384 (16 GiB), y un nivel para dispositivos de 12 GB especifica un límite inferior de 9216 (9 GiB). Esto garantiza que cada dispositivo coincida con su nivel previsto, independientemente de las variaciones de exclusión a nivel de la placa.

maximumAllowedMemTotal

Es el valor máximo de MemTotal del sistema (en MiB, límite exclusivo) para que se aplique este conjunto de límites. Si MemTotal es mayor o igual que este límite, no se aplica el límite establecido. En el caso de los dispositivos con 18,432 MiB o más, o con menos de 3,200 MiB, el limitador de memoria no aplica límites, por lo que los procesos no tienen restricciones.

memVisible

Es el límite de memoria flexible (memory.high) en MiB que se aplica a los procesos visibles.

memNotVisible

Es el límite de memoria flexible (memory.high) en MiB que se aplica a los procesos no visibles.

swapVisible

Es el límite de espacio de intercambio (memory.swap.max) en MiB que se aplica a los procesos visibles.

swapNotVisible

Es el límite de espacio de intercambio (memory.swap.max) en MiB que se aplica a los procesos no visibles.

Límites estándar de la plataforma

En Android 17 y versiones posteriores, la plataforma especifica límites estándar de memoria y de intercambio en /system/etc/memory-limiter-config.xml en los niveles de RAM física comunes con intervalos de rango de [floor, ceiling):

RAM física objetivo Rango de MemTotal [mínimo, máximo)
(minimumRequiredMemTotal,
maximumAllowedMemTotal)
Límite de memoria visible
(memVisible)
Límite de memoria no visible
(memNotVisible)
Límite de intercambio visible
(swapVisible)
Límite de intercambio no visible
(swapNotVisible)
16 GB [14336, 18432) MiB 10,240 MiB (10 GiB) 5,120 MiB (5 GiB) 5,120 MiB (5 GiB) 5,120 MiB (5 GiB)
12 GB [9216, 14336) MiB 8,192 MiB (8 GiB) 4,096 MiB (4 GiB) 4,096 MiB (4 GiB) 4,096 MiB (4 GiB)
8 GB [6800, 9216) MiB 5,120 MiB (5 GiB) 3,072 MiB (3 GiB) 3,072 MiB (3 GiB) 3,072 MiB (3 GiB)
6 GB [4800, 6800) MiB 4,096 MiB (4 GiB) 2,048 MiB (2 GiB) 2,048 MiB (2 GiB) 2,048 MiB (2 GiB)
4 GB [3200, 4800) MiB 2,048 MiB (2 GiB) 1,024 MiB (1 GiB) 1,024 MiB (1 GiB) 1,024 MiB (1 GiB)

Principios de los límites de memoria

La configuración del limitador de memoria se diseña en torno a los siguientes principios de la plataforma:

  • Coherencia del ecosistema y compatibilidad de las apps: Los límites de memoria coherentes en todos los dispositivos garantizan un rendimiento de la app predecible en todo el ecosistema de Android. Las apps se desarrollan y prueban según las expectativas de memoria estándar. Mantener límites uniformes de la plataforma evita limitaciones inesperadas o finalizaciones prematuras.

  • Asignación proporcional de recursos: Los límites de la plataforma se calibran según la capacidad física de la RAM:

    • Procesos visibles: Se asigna aproximadamente entre 1/2 y 2/3 de la RAM física total para admitir las cargas de trabajo de IU, composición y renderización activas.
    • Procesos no visibles: Se asigna aproximadamente entre 1/4 y 1/3 de la RAM física total para las tareas en segundo plano.
  • Aplicabilidad universal: Los límites se aplican de manera uniforme a todos los procesos de la aplicación en el dispositivo (UID >= 10000), incluidas las apps preinstaladas del sistema y del OEM. Además del servicio de inferencia de zona de pruebas integrado en el dispositivo predeterminado, el Limitador de memoria no exime a las apps individuales, lo que garantiza una administración justa de la memoria en todas las apps. Para configurar límites mínimos de protección de memory.min con ajuste automático en daemons interactivos críticos y límites máximos de memory.high más estrictos y calibrados para la carga de trabajo en apps y servicios individuales del sistema, consulta Presupuestos de memoria del sistema.

  • Ausencia de APIs de consulta de tiempo de ejecución: En Android 17 y versiones posteriores, las apps no pueden consultar de forma programática sus límites de memoria asignados en el tiempo de ejecución. Los límites estándar de la plataforma se definen con márgenes generosos para que las apps que se comportan de forma correcta funcionen sin restricciones durante el uso normal.

  • Dinámica de recuperación y de intercambio del kernel: Cuando un proceso de la app se acerca a su límite de memory.high, el kernel de Linux dirige la recuperación de memoria específicamente a ese cgroup de proceso. Esto incluye descartar las páginas inactivas respaldadas por archivos y intercambiar páginas anónimas inactivas a ZRAM, lo que mantiene la fluidez del sistema sin imponer una presión de memoria global.

Comandos del shell

El comando am memory-limiter permite que los desarrolladores y los integradores de sistemas interactúen con el servicio en el entorno de ejecución para el desarrollo, las pruebas y la depuración:

am memory-limiter <SUB-COMMAND>

estado

El subcomando status informa el estado operativo y las métricas activas del limitador de memoria:

adb shell am memory-limiter status

A continuación, se muestra un resultado de ejemplo:

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

Entre los campos clave del resultado, se incluyen los siguientes:

monitoring
Indica si el limitador de memoria está supervisando los procesos de forma activa.
visibleMem y notVisibleMem
Son los límites de memoria absolutos calculados (memory.high) que se aplican actualmente para cada estado de visibilidad.
visibleSwap y notVisibleSwap
Los límites de intercambio absolutos calculados (memory.swap.max) que se aplican actualmente para cada estado de visibilidad.
events
Es la cantidad de veces que un proceso superó el límite asignado.
processes
Es la cantidad actual de procesos supervisados.

ignore

El subcomando ignore excluye temporalmente un UID específico o todos los procesos de la limitación de memoria. Esto es útil para realizar comparativas de rendimiento, pruebas de esfuerzo o diagnósticos del comportamiento de la memoria:

# 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

El subcomando manual anula los límites calculados para un proceso específico por ID de proceso (PID) con un valor absoluto personalizado en bytes. El valor debe ser un número entero, pero puede incluir el sufijo MB para indicar MiB o el sufijo GB para indicar GiB:

# 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

Las anulaciones manuales se aplican solo durante el ciclo de vida de esa instancia de proceso específica. Si el proceso se reinicia, vuelve a los límites estándar según su estado.

Una anulación manual no puede superar los límites físicos del sistema.