Limitador de memória

O Android 17 e versões mais recentes incluem o Memory Limiter, um serviço do sistema que monitora e limita o uso da memória dos processos de aplicativos usando o cgroup v2 do Linux. O Memory Limiter impede que apps individuais consumam memória do sistema de forma desproporcional, o que reduz a pressão geral da memória e evita encerramentos agressivos de processos críticos em primeiro plano e em cache devido à pouca memória.

Mecanismo

O Memory Limiter se integra ao Activity Manager Service (AMS) para rastrear eventos do ciclo de vida do processo e mudanças de estado. O Memory Limiter aplica limites de memória usando a hierarquia do cgroup v2 do kernel do Linux.

Para oferecer suporte ao Memory Limiter, o kernel do dispositivo precisa ativar o cgroup v2 e o controlador memory. O serviço depende especificamente dos seguintes atributos:

memory.high
Um limite flexível. Quando excedido, o processo é limitado e o kernel tenta recuperar a página de forma proativa desse processo.
memory.swap.max
Limita a quantidade máxima de espaço de troca (como ZRAM) que o processo pode usar.

Impacto nos apps

Os apps que operam dentro dos limites de memória não são afetados pelo Memory Limiter.

Quando um app ultrapassa o limite de memory.high, o kernel remove a memória limpa com suporte a arquivos do app e troca a memória anônima inativa para manter o app dentro do limite. Como resultado dessa remoção de página e atividade de troca, o app pode sofrer limitação temporária de execução, mas continua em execução.

Em situações extremas, como um vazamento de memória descontrolado em que o app continua alocando memória anônima após o espaço de troca ser esgotado, o app não consegue alocar memória e é encerrado.

Monitoramento de processos

O Memory Limiter monitora os processos de apps (UID >= 10000) por padrão. Os processos principais do sistema são isentos para preservar a estabilidade essencial do sistema.

O Memory Limiter atribui limites de memória com base no estado de visibilidade do processo:

  • Processos visíveis:processos que hospedam uma interface visível ao usuário (como a atividade focada no momento ou uma interação ativa). Como a exibição de uma interface exige um conjunto de trabalho maior para renderização e pipelines gráficos, os processos visíveis recebem um limite de memória mais generoso.

  • Processos não visíveis:processos que realizam o trabalho em segundo plano sem apresentar uma interface (como serviços em segundo plano ou receptores de transmissão). Como eles não renderizam componentes de interface, operam com um limite mais restritivo.

A tabela a seguir mapeia estados de processo específicos para classificações de limite de memória:

Estado do processoLimite de memória
PERSISTENTIrrestrito
PERSISTENT_UIIrrestrito
TOPVisível
BOUND_TOPVisível
FOREGROUND_SERVICENão visível
BOUND_FOREGROUND_SERVICENão visível
IMPORTANT_FOREGROUNDVisível
IMPORTANT_BACKGROUNDNão visível
TRANSIENT_BACKGROUNDNão visível
BACKUPNão visível
SERVICENão visível
RECEIVERNão visível
TOP_SLEEPINGVisível
HEAVY_WEIGHTNão visível
HOMENão visível
LAST_ACTIVITYNão visível
CACHED_ACTIVITYEm cache
CACHED_ACTIVITY_CLIENTEm cache
CACHED_RECENTEm cache
CACHED_EMPTYEm cache

No estado em cache, os processos são congelados e recuperados ao máximo.

Quando um processo excede o limite de memory.high atribuído, o Memory Limiter detecta o evento e pode acionar ações de diagnóstico, como capturar um perfil de memória ou registrar uma anomalia no statsd.

Configuração

No Android 17 e versões mais recentes, o Memory Limiter fornece um arquivo de configuração padrão da plataforma na partição do sistema, oferecendo suporte a substituições opcionais específicas do dispositivo na partição vendor:

  • Configuração padrão do sistema:/system/etc/memory-limiter-config.xml
  • Substituição do fornecedor (opcional) : /vendor/etc/memory-limiter-config.xml

O Memory Limiter verifica primeiro o arquivo /vendor/etc/memory-limiter-config.xml. Se presente, ele usa a configuração do fornecedor. Caso contrário, ele volta para a configuração padrão do sistema em /system/etc/memory-limiter-config.xml.

Formato XML

O arquivo de configuração segue o esquema definido em memory-limiter-config.xsd. O arquivo define vários conjuntos de limites ordenados pela memória disponível. O serviço seleciona o conjunto de limites correspondente mais alto com base na RAM disponível do dispositivo. Todos os valores de memória são especificados em 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
Um número inteiro positivo que identifica a versão da configuração. Esse valor precisa ser 1.
minimumRequiredMemTotal

O MemTotal mínimo do sistema (em MiB) necessário para que esse conjunto de limites seja aplicado. O serviço avalia isso em relação à memória total disponível informada pelo kernel em /proc/meminfo.

Entender as reservas de memória:o valor MemTotal do kernel reflete a DRAM física acessível ao sistema operacional após a dedução das reservas de memória de hardware e firmware. As reservas reservam RAM para componentes de hardware especializados, como GPU, modem de banda base, ISP da câmera, ambientes de execução seguros e hipervisores, geralmente consumindo entre 500 MiB e mais de 1 GiB de memória física.

Como MemTotal é menor que a DRAM física anunciada do dispositivo, minimumRequiredMemTotal os limites incluem uma margem para essas reservas. Por exemplo, uma configuração direcionada a dispositivos de 16 GB especifica um minimumRequiredMemTotal de 13312 (13 GiB) em vez de 16384 (16 GiB), e um nível para dispositivos de 12 GB especifica 10240 (10 GiB). Isso garante que cada dispositivo corresponda ao nível pretendido, independentemente das variações de reserva no nível da placa.

memVisible

O limite de memória flexível (memory.high) em MiB aplicado a processos visíveis.

memNotVisible

O limite de memória flexível (memory.high) em MiB aplicado a processos não visíveis.

swapVisible

O limite de espaço de troca (memory.swap.max) em MiB aplicado a processos visíveis.

swapNotVisible

O limite de espaço de troca (memory.swap.max) em MiB aplicado a processos não visíveis.

Limites padrão da plataforma

No Android 17 e versões mais recentes, a plataforma especifica limites padrão de memória e troca em /system/etc/memory-limiter-config.xml em níveis comuns de RAM física:

RAM física de destino MemTotal
mínimo necessário
(minimumRequiredMemTotal)
Limite de memória visível
(memVisible)
Limite de memória não visível
(memNotVisible)
Limite de troca visível
(swapVisible)
Limite de troca não visível
(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)

Princípios de limite de memória

A configuração do Memory Limiter foi projetada com base nos seguintes princípios da plataforma:

  • Consistência do ecossistema e compatibilidade de apps:limites de memória consistentes em todos os dispositivos garantem a performance previsível do app em todo o ecossistema Android. Os apps são desenvolvidos e testados em relação às expectativas de memória padrão. A manutenção de limites uniformes da plataforma evita a limitação inesperada ou encerramentos prematuros.

  • Alocação proporcional de recursos:os limites da plataforma são calibrados de acordo com a capacidade de RAM física:

    • Processos visíveis:aproximadamente 1/2 a 2/3 da RAM física total alocada para oferecer suporte a cargas de trabalho ativas de interface, composição e renderização.
    • Processos não visíveis:aproximadamente 1/4 a 1/3 da RAM física total alocada para tarefas em segundo plano.
  • Aplicabilidade universal:os limites são aplicados uniformemente a todos os processos de aplicativos no dispositivo (UID >= 10000), incluindo apps do sistema e OEM pré-instalados. O Memory Limiter não oferece suporte a listas de permissões para isentar apps específicos, garantindo uma governança de memória justa em todos os apps.

  • Ausência de APIs de consulta de tempo de execução:no Android 17 e versões mais recentes, os apps não podem consultar programaticamente os limites de memória atribuídos no tempo de execução. Os limites padrão da plataforma são definidos com margens generosas para que os apps bem-comportados operem sem restrições durante o uso normal.

  • Recuperação do kernel e dinâmica de troca:quando um processo de app se aproxima do limite de memory.high, o kernel do Linux direciona a recuperação de memória especificamente para esse cgroup de processo. Isso inclui descartar páginas inativas com suporte a arquivos e trocar páginas anônimas frias para ZRAM, mantendo a fluidez do sistema sem impor pressão global de memória.

Comandos do shell

O comando am memory-limiter permite que desenvolvedores e integradores de sistemas interajam com o serviço no tempo de execução para desenvolvimento, testes e depuração:

am memory-limiter <SUB-COMMAND>

status

O subcomando status informa o status operacional e as métricas ativas do Memory Limiter:

adb shell am memory-limiter status

Exemplo de saída:

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

Os principais campos na saída incluem:

monitoring
Indica se o Memory Limiter está monitorando ativamente os processos.
visibleMem e notVisibleMem
Os limites de memória absolutos calculados (memory.high) atualmente aplicados a cada estado de visibilidade.
visibleSwap e notVisibleSwap
Os limites de troca absolutos calculados (memory.swap.max) atualmente aplicados a cada estado de visibilidade.
events
O número de vezes que um processo ultrapassou o limite atribuído.
processes
O número atual de processos monitorados.

ignorar

O subcomando ignore exclui temporariamente um UID específico ou todos os processos da limitação de memória. Isso é útil para benchmarking de performance, testes de estresse ou diagnóstico de comportamento de memória:

# 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

O subcomando manual substitui os limites calculados de um processo específico pelo ID do processo (PID) com um valor absoluto personalizado em bytes. O valor precisa ser um número inteiro, mas pode incluir o sufixo MB para indicar MiB ou o sufixo 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

As substituições manuais se aplicam apenas à vida útil dessa instância de processo específica. Se o processo for reiniciado, ele retornará aos limites padrão com base no estado.

Uma substituição manual não pode exceder os limites físicos do sistema.