Limitador de memoria

Android 17 y las versiones posteriores incluyen el Limitador de memoria, un servicio del sistema que supervisa y limita el uso de memoria de los procesos de la aplicación con cgroup v2 de Linux. El Limitador de memoria evita que las apps individuales consuman una cantidad desproporcionada de memoria del sistema, 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 almacenados en caché cuando hay poca memoria.

Mecanismos

El Limitador de memoria se integra con el Servicio de administrador de actividades (AMS) para hacer un seguimiento de los eventos del ciclo de vida del proceso y los cambios de estado. El Limitador de memoria aplica los límites de memoria con la jerarquía 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
Es un límite flexible. Cuando se supera, se limita el proceso y el kernel intenta recuperar la página 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

El Limitador de memoria no afecta a las apps que operan dentro de sus límites de memoria.

Cuando una app supera su límite 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 expulsión de página y actividad de intercambio, la app podría experimentar una limitación temporal de la ejecución, pero continúa ejecutándose.

En situaciones extremas, como una fuga de memoria descontrolada en la que la app continúa 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

De forma predeterminada, el Limitador de memoria supervisa los procesos de la app (UID >= 10000). Los procesos principales del sistema están exentos para preservar la estabilidad esencial 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 la renderización y las canalizaciones de 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 transmisión). Debido a que 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 clasificaciones de límite de memoria:

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é

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

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

Configuración

En Android 17 y versiones posteriores, el Limitador de memoria proporciona un archivo de configuración predeterminado de la plataforma estándar en la partición del sistema, mientras que admite anulaciones opcionales específicas del dispositivo en la partición vendor:

  • Configuración predeterminada del sistema: /system/etc/memory-limiter-config.xml
  • Anulación del proveedor (opcional): /vendor/etc/memory-limiter-config.xml

El Limitador de memoria primero busca /vendor/etc/memory-limiter-config.xml. Si está presente, usa la configuración del proveedor; de lo contrario, vuelve a la configuración predeterminada 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 coincidentes más alto según la RAM disponible del dispositivo. Todos los valores de memoria se especifican en mebibytes (MiB).

<MemoryLimiterConfig>
  <version>1</version>
  <configList>
    <!-- RAM minimums account for MemTotal excluding carve-outs. -->
    <limitSet>
      <!-- Limits for 16GB RAM device (MemTotal >= 13GiB): 10G/5G/5G/5G -->
      <minimumRequiredMemTotal>13312</minimumRequiredMemTotal>
      <memVisible>10240</memVisible>
      <memNotVisible>5120</memNotVisible>
      <swapVisible>5120</swapVisible>
      <swapNotVisible>5120</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 12GB RAM device (MemTotal >= 10GiB): 8G/4G/4G/4G -->
      <minimumRequiredMemTotal>10240</minimumRequiredMemTotal>
      <memVisible>8192</memVisible>
      <memNotVisible>4096</memNotVisible>
      <swapVisible>4096</swapVisible>
      <swapNotVisible>4096</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 8GB RAM device (MemTotal >= 6.5GiB): 5G/3G/3G/3G -->
      <minimumRequiredMemTotal>6656</minimumRequiredMemTotal>
      <memVisible>5120</memVisible>
      <memNotVisible>3072</memNotVisible>
      <swapVisible>3072</swapVisible>
      <swapNotVisible>3072</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 6GB RAM device (MemTotal >= 4.5GiB): 4G/2G/2G/2G -->
      <minimumRequiredMemTotal>4608</minimumRequiredMemTotal>
      <memVisible>4096</memVisible>
      <memNotVisible>2048</memNotVisible>
      <swapVisible>2048</swapVisible>
      <swapNotVisible>2048</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 4GB RAM device (MemTotal >= 3GiB): 2G/1G/1G/1G -->
      <minimumRequiredMemTotal>3072</minimumRequiredMemTotal>
      <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 configuración. Debe ser 1.
minimumRequiredMemTotal

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

Información sobre 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 que se deducen las asignaciones de memoria de hardware y firmware. Las asignaciones reservan 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, que suelen consumir entre 500 MiB y más de 1 GiB de memoria física.

Debido a que MemTotal es más bajo que la DRAM física anunciada del dispositivo, minimumRequiredMemTotal los umbrales incluyen un margen para estas asignaciones. Por ejemplo, una configuración orientada a dispositivos de 16 GB especifica un minimumRequiredMemTotal de 13312 (13 GiB) en lugar de 16384 (16 GiB), y un nivel para dispositivos de 12 GB especifica 10240 (10 GiB). Esto garantiza que cada dispositivo coincida con el nivel previsto, independientemente de las variaciones de asignación a nivel de la placa.

memVisible

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

memNotVisible

Es el límite flexible de memoria (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 comunes de RAM física:

RAM física de destino MemTotal
requerida mínima
(minimumRequiredMemTotal)
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 13,312 MiB (13 GiB) 10,240 MiB (10 GiB) 5,120 MiB (5 GiB) 5,120 MiB (5 GiB) 5,120 MiB (5 GiB)
12 GB 10,240 MiB (10 GiB) 8,192 MiB (8 GiB) 4,096 MiB (4 GiB) 4,096 MiB (4 GiB) 4,096 MiB (4 GiB)
8 GB 6,656 MiB (6.5 GiB) 5,120 MiB (5 GiB) 3,072 MiB (3 GiB) 3,072 MiB (3 GiB) 3,072 MiB (3 GiB)
6 GB 4,608 MiB (4.5 GiB) 4,096 MiB (4 GiB) 2,048 MiB (2 GiB) 2,048 MiB (2 GiB) 2,048 MiB (2 GiB)
4 GB 3,072 MiB (3 GiB) 2,048 MiB (2 GiB) 1,024 MiB (1 GiB) 1,024 MiB (1 GiB) 1,024 MiB (1 GiB)

Principios del límite de memoria

La configuración del Limitador de memoria se diseñó en función de los siguientes principios de la plataforma:

  • Coherencia del ecosistema y compatibilidad de apps: Los límites de memoria coherentes en todos los dispositivos garantizan un rendimiento predecible de las apps en todo el ecosistema de Android. Las apps se desarrollan y prueban en función de las expectativas de memoria estándar; mantener límites uniformes de la plataforma evita la limitación inesperada o las finalizaciones prematuras.

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

    • Procesos visibles: Se asigna aproximadamente de 1/2 a 2/3 de la RAM física total para admitir cargas de trabajo activas de IU, composición y renderización.
    • Procesos no visibles: Se asigna aproximadamente de 1/4 a 1/3 de la RAM física total para 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. El Limitador de memoria no admite listas de entidades permitidas para eximir apps específicas, lo que garantiza una administración justa de la memoria en todas las apps.

  • Ausencia de APIs de consulta en 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 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 bien operen sin restricciones durante el uso normal.

  • Recuperación del kernel y dinámica de intercambio: Cuando un proceso de la app se acerca a su límite memory.high, el kernel de Linux dirige la recuperación de memoria específicamente a ese cgroup de proceso. Esto incluye descartar páginas inactivas respaldadas por archivos y cambiar páginas anónimas inactivas a ZRAM, lo que mantiene la fluidez del sistema sin imponer 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 tiempo 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

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á observando activamente los procesos.
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
Son 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ó su 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 la evaluación comparativa del rendimiento, las pruebas de esfuerzo o el diagnóstico 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 solo se aplican durante la vida útil de esa instancia de proceso específica. Si se reinicia el proceso, 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.