Limitador de memória

O Android 17 e versões mais recentes incluem o Limitador de Memória, um serviço do sistema que monitora e limita o uso da memória de processos de aplicativos usando o cgroup v2 do Linux. O Limitador de Memória impede que apps individuais consumam memória do sistema de forma desproporcional, o que reduz a pressão geral sobre a memória e evita o encerramento agressivo de processos críticos em primeiro plano e em cache por falta de memória.

Mecanismo

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

Para oferecer suporte ao limitador de memória, 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 páginas proativamente 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 Limitador de Memória.

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 atividade de remoção e troca de páginas, o app pode sofrer uma limitação temporária de execução, mas continua em execução.

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

Monitoramento de processos

O limitador de memória monitora os processos do app (UID >= 10000) por padrão. Os processos principais do sistema estão isentos para preservar a estabilidade essencial do sistema.

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

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

  • Processos não visíveis:processos que realizam trabalho em segundo plano sem apresentar uma interface, como serviços em segundo plano ou broadcast receivers. Como eles não renderizam componentes de IU, operam sob 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, em seguida, recuperados ao máximo.

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

Configuração

No Android 17 e versões mais recentes, o Limitador de Memória é configurado usando o arquivo de configuração padrão da plataforma na partiçã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 por 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 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
Um número inteiro positivo que identifica a versão da configuração. Precisa ser 1.
minimumRequiredMemTotal

O MemTotal mínimo do sistema (em MiB, incluindo o piso) 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.

Noções básicas sobre as divisões e os limites de memória:o valor MemTotal do kernel reflete a DRAM física acessível ao sistema operacional após a dedução das divisões de memória de hardware e firmware. As exclusões reservam RAM para componentes de hardware especializados, como GPU, modem de banda base, ISP de câmera, ambientes de execução seguros e hipervisores, normalmente consumindo entre 500 MiB e mais de 2 GiB de memória física.

Como MemTotal é menor do que a DRAM física anunciada do dispositivo, os limites de minimumRequiredMemTotal incluem uma margem para essas reduções. Por exemplo, uma configuração para dispositivos de 16 GB especifica um limite mínimo de 14336 (14 GiB) em vez de 16384 (16 GiB), e um nível para dispositivos de 12 GB especifica um limite mínimo de 9216 (9 GiB). Isso garante que cada dispositivo corresponda ao nível pretendido, independentemente das variações de separação no nível da placa.

maximumAllowedMemTotal

O MemTotal máximo do sistema (em MiB, limite exclusivo) para que esse conjunto de limites seja aplicado. Se MemTotal for maior ou igual a esse teto, o limite definido não será aplicado. Para dispositivos com 18.432 MiB ou mais ou com menos de 3.200 MiB, o Limitador de Memória não aplica limites, deixando os processos sem restrições.

memVisible

O limite de memória flexível (memory.high) em MiB aplicado aos 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 aos 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 usando intervalos de [floor, ceiling):

RAM física de destino Intervalo de MemTotal [mínimo, máximo)
(minimumRequiredMemTotal,
maximumAllowedMemTotal)
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 [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)

Princípios de limite de memória

A configuração do limitador de memória 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 um desempenho previsível do app em todo o ecossistema Android. Os apps são desenvolvidos e testados de acordo com as expectativas de memória padrão. Manter limites uniformes da plataforma evita limitações inesperadas ou encerramentos prematuros.

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

    • Processos visíveis:alocação de aproximadamente 1/2 a 2/3 do total de RAM física para oferecer suporte a cargas de trabalho ativas de interface, composição e renderização.
    • Processos não visíveis:alocam aproximadamente de 1/4 a 1/3 do total de RAM física para tarefas em segundo plano.
  • Aplicabilidade universal:os limites se aplicam de maneira uniforme a todos os processos de aplicativos no dispositivo (UID >= 10000), incluindo apps pré-instalados do sistema e do OEM. O Limitador de Memória não é compatível com listas de permissões para isentar apps específicos, garantindo uma governança de memória justa em todos os apps. Para configurar limites de proteção de memory.min com ajuste automático em daemons interativos críticos e limites de memory.high mais rígidos e calibrados para cargas de trabalho em apps e serviços individuais do sistema, consulte Orçamentos de memória do sistema.

  • Ausência de APIs de consulta de ambiente de execução:no Android 17 e versões mais recentes, os apps não podem consultar de forma programática os limites de memória atribuídos no ambiente 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.

  • Dinâmica de recuperação e troca do kernel: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 backup de arquivos e trocar páginas anônimas frias para a ZRAM, mantendo a fluidez do sistema sem impor pressão de memória global.

Comandos do shell

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

am memory-limiter <SUB-COMMAND>

status

O subcomando status informa o status de funcionamento e as métricas ativas do limitador de memória:

adb shell am memory-limiter status

Veja um 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 campos principais na saída incluem:

monitoring
Indica se o Limitador de Memória está monitorando os processos ativamente.
visibleMem e notVisibleMem
Os limites absolutos de memória calculados (memory.high) atualmente aplicados a cada estado de visibilidade.
visibleSwap e notVisibleSwap
Os limites absolutos de troca calculados (memory.swap.max) atualmente aplicados para 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 comparativos de desempenho, 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 para um processo específico por ID de processo (PID) por 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 ao ciclo de vida dessa instância de processo específica. Se o processo for reiniciado, ele vai retornar aos limites padrão com base no estado dele.

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