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 processo | Limite de memória |
|---|---|
PERSISTENT | Irrestrito |
PERSISTENT_UI | Irrestrito |
TOP | Visível |
BOUND_TOP | Visível |
FOREGROUND_SERVICE | Não visível |
BOUND_FOREGROUND_SERVICE | Não visível |
IMPORTANT_FOREGROUND | Visível |
IMPORTANT_BACKGROUND | Não visível |
TRANSIENT_BACKGROUND | Não visível |
BACKUP | Não visível |
SERVICE | Não visível |
RECEIVER | Não visível |
TOP_SLEEPING | Visível |
HEAVY_WEIGHT | Não visível |
HOME | Não visível |
LAST_ACTIVITY | Não visível |
CACHED_ACTIVITY | Em cache |
CACHED_ACTIVITY_CLIENT | Em cache |
CACHED_RECENT | Em cache |
CACHED_EMPTY | Em 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. minimumRequiredMemTotalO
MemTotalmí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
MemTotaldo 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 deminimumRequiredMemTotalincluem uma margem para essas reduções. Por exemplo, uma configuração para dispositivos de 16 GB especifica um limite mínimo de14336(14 GiB) em vez de16384(16 GiB), e um nível para dispositivos de 12 GB especifica um limite mínimo de9216(9 GiB). Isso garante que cada dispositivo corresponda ao nível pretendido, independentemente das variações de separação no nível da placa.maximumAllowedMemTotalO
MemTotalmáximo do sistema (em MiB, limite exclusivo) para que esse conjunto de limites seja aplicado. SeMemTotalfor 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.memVisibleO limite de memória flexível (
memory.high) em MiB aplicado aos processos visíveis.memNotVisibleO limite de memória flexível (
memory.high) em MiB aplicado a processos não visíveis.swapVisibleO limite de espaço de troca (
memory.swap.max) em MiB aplicado aos processos visíveis.swapNotVisibleO 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.mincom ajuste automático em daemons interativos críticos e limites dememory.highmais 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 statusVeja 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.
visibleMemenotVisibleMem- Os limites absolutos de memória calculados (
memory.high) atualmente aplicados a cada estado de visibilidade. visibleSwapenotVisibleSwap- 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.