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 da 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 de recuperação flexível aplicado ao cgroup do UID do pacote ou a um cgroup do PID do processo quando um orçamento de memória por processo está ativo. O padrão é 90% do limite de memória da plataforma, a menos que seja reduzido por um orçamento de memória. Quando excedido, o kernel limita as alocações e recupera páginas desse cgroup.
memory.max- Um limite rígido de memória aplicado ao cgroup do UID do pacote em 100% do
limite de memória da plataforma. Se a recuperação não conseguir manter o uso da memória abaixo de
memory.max, o kernel vai encerrar o processo de falta de memória. memory.swap.max- Limita o espaço de troca máximo, como ZRAM, que cada cgroup de PID de processo pode usar.
Requisitos de cgroup e kernel
Os dispositivos com o Android 17 (nível 37 da API) e versões mais recentes precisam atender aos seguintes
requisitos de cgroup v2 e kernel para limites de plataforma, orçamentos de manifesto
e MemoryBudgetManager no nível 37.2 da API e
versões mais recentes:
Ativação do cgroup por processo: mantenha
"NeedsActivation": trueno controladormemorydo cgroup v2 em/system/etc/cgroups.jsonpara que oinitinicial ative+memoryantes de criar/sys/fs/cgroup/apps/uid_<UID>/pid_<PID>e/sys/fs/cgroup/system/uid_<UID>/pid_<PID>. Não desative"NeedsActivation", definaro.config.per_app_memcg=falsenem adicione substituições de fornecedorcgroups.jsonque desativem cgroups de memória por UID ou por processo. Se/vendor/etc/task_profiles.jsonsubstituir atributos do controlador de memória, comoMemLimitouMemSoftLimit, mantenha"File"não vazio e inclua mapeamentos de"FileV2"nos nomes de arquivo do cgroup v2.Permissões de arquivo do Cgroup: os arquivos de controle
memory.high,memory.maxememory.swap.maxpaiuid_<UID>e por processopid_<PID>em/sys/fs/cgroup/appse/sys/fs/cgroup/systemprecisam ser graváveis porsystem_serverno UID ou GID 1000 para que os limites de UID, os orçamentos de processo e os limites de troca sejam aplicados sem errosEACCES. Os arquivosmemory.current,memory.events,memory.events.local,memory.statememory.highpor UID e por processo, além dememory.maxememory.swap.maxpor UID, precisam ser legíveis pelo UID do app proprietário para que as consultasMemoryBudgetManagere os listeners acima do orçamento operem no processo.Isolamento de PSI local: isole as paradas de recuperação por cgroup
memory.highdas informações de parada de pressão de memória global (PSI, na sigla em inglês) incluindo a mudança de PSI do memcg local da GKI e mantendorecord_psi=falseem qualquer gancho de fornecedorandroid_vh_mem_cgroup_handle_over_high. Sem o isolamento de PSI, a limitação localizada dememory.highaumenta o PSI de memória global e pode acionar encerramentos desnecessários de processoslmkdquando a memória de todo o sistema não está sob pressão.
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 descontrolado em que o app
continua alocando memória além de memory.max ou 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 aplica os limites da plataforma nesta página aos processos do app
(UID >= 10000). Os processos com um UID abaixo de 10000 não recebem limites de plataforma,
nem o pacote do serviço de inferência em sandbox padrão no dispositivo (config_defaultOnDeviceSandboxedInferenceService). Os apps e serviços do sistema
ainda podem declarar orçamentos de memória mais restritos, conforme descrito em
Orçamentos de memória 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 da plataforma:
| Estado do processo | Limite de memória |
|---|---|
PERSISTENT |
Irrestrito |
PERSISTENT_UI |
Irrestrito |
TOP |
visible |
BOUND_TOP |
visible |
FOREGROUND_SERVICE |
not-visible |
BOUND_FOREGROUND_SERVICE |
not-visible |
IMPORTANT_FOREGROUND |
visible |
IMPORTANT_BACKGROUND |
not-visible |
TRANSIENT_BACKGROUND |
not-visible |
BACKUP |
not-visible |
SERVICE |
not-visible |
RECEIVER |
not-visible |
TOP_SLEEPING |
visible |
HEAVY_WEIGHT |
not-visible |
HOME |
not-visible |
LAST_ACTIVITY |
not-visible |
CACHED_ACTIVITY |
not-visible |
CACHED_ACTIVITY_CLIENT |
not-visible |
CACHED_RECENT |
not-visible |
CACHED_EMPTY |
not-visible |
Os processos PERSISTENT e PERSISTENT_UI não recebem um limite de plataforma. Se
um desses processos declarar um orçamento de memória, o orçamento em primeiro plano será aplicado,
conforme descrito em Buckets de orçamento de memória.
No estado em cache, os processos mantêm o limite da plataforma not-visible,
além do congelamento e da compactação de apps em cache, mantendo-os limitados mesmo que
o congelamento ou a recuperação máxima não ocorram.
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.
Buckets de orçamento de memória
No nível 37.2 da API e versões mais recentes, os apps e os serviços do sistema podem declarar orçamentos de memória
mais restritos do que os limites da plataforma ou definir orçamentos de execução mais restritos
com MemoryBudgetManager. Os apps configuram
orçamentos conforme descrito em Definir orçamentos de memória do app. Para
apps e serviços do sistema, consulte Orçamentos de memória do sistema. Um
orçamento define memory.high e não pode exceder o limite da plataforma para o mesmo
estado do processo, e um orçamento de ambiente de execução não pode exceder um orçamento de manifesto aplicável.
Quando um processo declara um orçamento de manifesto, o Limitador de Memória mapeia o estado do processo
para um dos três buckets de orçamento, enquanto um orçamento de ambiente de execução aplica um único
limite até que seja atualizado ou limpo. Os valores de manifesto android:state
foreground, perceptible e background especificam o orçamento para o
bucket correspondente. Os processos em cache nos estados de CACHED_ACTIVITY a
CACHED_EMPTY mantêm o orçamento de background, além do congelamento e da compactação
de apps em cache.
| Estado do processo | Intervalo de orçamento |
|---|---|
PERSISTENT | Primeiro plano |
PERSISTENT_UI | Primeiro plano |
TOP | Primeiro plano |
BOUND_TOP | Primeiro plano |
TOP_SLEEPING | Primeiro plano |
FOREGROUND_SERVICE | Perceptível |
BOUND_FOREGROUND_SERVICE | Perceptível |
IMPORTANT_FOREGROUND | Perceptível |
IMPORTANT_BACKGROUND | Perceptível |
TRANSIENT_BACKGROUND | Contexto |
BACKUP | Contexto |
SERVICE | Contexto |
RECEIVER | Contexto |
HEAVY_WEIGHT | Contexto |
HOME | Contexto |
LAST_ACTIVITY | Contexto |
CACHED_ACTIVITY | Contexto |
CACHED_ACTIVITY_CLIENT | Contexto |
CACHED_RECENT | Contexto |
CACHED_EMPTY | Contexto |
Requisitos de implementação do dispositivo
Os orçamentos pertencem ao app. Um app pode definir, mudar ou limpar os próprios orçamentos de execução
usando MemoryBudgetManager, por exemplo, em resposta à própria
configuração remota, e só muda os orçamentos do manifesto por uma atualização
do app. As implementações de dispositivos, incluindo mudanças em system_server, serviços
de fornecedores e indicadores de um framework de OEM ou app complementar, não podem definir,
aumentar, limpar ou ignorar o orçamento de manifesto ou de execução de um app em nome
dele. Isso não impede que os OEMs declarem ou recalibrem orçamentos nos
manifestos dos apps do sistema que criam, conforme descrito em
Orçamentos de memória do sistema.
A seção 8.6 do Documento de definição de compatibilidade do Android proíbe métodos, listas de permissões ou políticas que ignoram os limites de memória do app. As versões de produção não podem incluir listas de permissões, substituições por app ou chaves globais que desativem os limites de memória da plataforma ou os orçamentos de memória do app.
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 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 as divisões de memória de hardware e firmware serem deduzidas. 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 carve-out no nível da placa.maximumAllowedMemTotalO
MemTotalmáximo do sistema (em MiB, teto 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 comMemTotalmenor que 3.200 MiB ou pelo menos 18.432 MiB, o Limitador de Memória não aplica os limites padrão da plataforma, mas os cgroups de memória por processo permanecem ativos (junto com os orçamentos de manifesto, sistema e ambiente de execuçãoMemoryBudgetManagerno nível 37.2 da API e versões mais recentes).memVisibleO limite de memória rígido de
memory.maxem MiB aplicado ao cgroup do UID do pacote quando o pacote tem um processo visível. O limite padrão de recuperação flexível dememory.highé 90% desse valor, arredondado para o MiB mais próximo.memNotVisibleO limite de memória rígido de
memory.maxem MiB aplicado ao cgroup do UID do pacote quando o pacote não tem processos visíveis. O limite padrão de recuperação flexível dememory.highé 90% desse valor, arredondado para o MiB mais próximo.swapVisibleO limite de espaço de troca
memory.swap.maxem MiB aplicado a cada cgroup de PID de processo visível.swapNotVisibleO limite de espaço de troca de
memory.swap.maxem MiB aplicado a cada cgroup de PID de processo não visível.
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: alocam aproximadamente de 1/2 a 2/3 da RAM física total para dar 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. Além do serviço de inferência em sandbox padrão no dispositivo, o limitador de memória não isenta apps individuais, garantindo uma governança de memória justa em todos os apps. Para configurar limites de proteção de ajuste automático de
memory.minem 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.Orçamentos de ambiente de execução previsíveis: no nível 37.2 da API e em versões mais recentes, os apps podem consultar os limites de memória e o uso de memória do cgroup e definir orçamentos de ambiente de execução mais rígidos usando
MemoryBudgetManager. Preservar os limites padrão da plataforma e do manifesto sem substituições do fornecedor mantém o comportamento do orçamento de execução consistente em todos os dispositivos.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 o cgroup desse processo. Isso inclui descartar páginas inativas com suporte 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, os limites de plataforma configurados e os contadores de monitoramento ativos do limitador de memória:
adb shell am memory-limiter statusA execução de adb shell am memory-limiter status version mostra 2 quando o
limitador de memória de dois limites está ativado ou 0 quando está desativado.
Confira abaixo um exemplo de saída de status em um dispositivo de 8 GB:
Memory limiter
enabled limits=true killing=true ignored=none
visible: memHigh=4608MB memMax=5120MB swapMax=3072MB
not-visible: memHigh=2765MB memMax=3072MB swapMax=3072MB
active=64 uid-started=32 uid-current=28 uid-updated=4
started=72 watched=72 watch-failed=0
limiters=64 limiters-hwm=70 red-poll=0 red-limiter=0
events=0 false-events=0 modify=84
Os campos principais na saída incluem:
limitsekilling- Indica se os limites de memória da plataforma
memory.highememory.maxestão configurados e aplicados ao nível de RAM do dispositivo. ignored- O UID excluído pelo subcomando
ignore,allquando a limitação está pausada globalmente ounone. visibleenot-visible- O limite padrão de UID
memHighparamemory.high, o limite de UIDmemMaxparamemory.maxe o limite deswapMaxpor processo paramemory.swap.maxaplicado a cada classificação de visibilidade. active- O número de limitadores de processo e UID rastreados em
system_server. uid-started,uid-currenteuid-updated- A contagem cumulativa de iniciados, a contagem de ativos e a contagem cumulativa de substituições para limitadores de UID atualizados com orçamentos de manifesto.
watchedewatch-failed- O número cumulativo de observações de cgroup
memory.eventsregistradas e com falha. limiterselimiters-hwm- O número e o limite máximo de observadores de cgroup nativos ativos.
eventsefalse-events- As contagens de eventos acima do limite enviados para notificações de
system_serverememory.eventsque não ultrapassaram um limite monitorado.
ignorar
O subcomando ignore exclui temporariamente um UID específico ou todos os processos
da limitação de memória. Isso é útil para comparar o desempenho, fazer testes de estresse,
calibrar automaticamente o orçamento de memória do app
ou diagnosticar o comportamento da 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 memory.high, memory.max
e memory.swap.max por um teto inteiro personalizado até os limites configurados
do destino. Transmita um PID, com ou sem -p, para segmentar um único
processo ou transmita -u <UID> para segmentar todos os processos em um UID de pacote. Especifique
o tamanho em bytes, anexe MB para MiB ou GB para GiB ou transmita none para limpar
uma substituição. Para compatibilidade com versões anteriores, números inteiros simples abaixo de 20 sem um
sufixo são interpretados como MiB:
# Set a 1 GiB limit for PID 1234, with or without -p adb shell am memory-limiter manual 1234 1073741824 adb shell am memory-limiter manual 1234 1024MB adb shell am memory-limiter manual -p 1234 1GB# Set a 1 GiB limit for all processes in UID 10087 adb shell am memory-limiter manual -u 10087 1GB# Remove the manual override for PID 1234 or UID 10087 adb shell am memory-limiter manual 1234 none adb shell am memory-limiter manual -u 10087 none
As substituições manuais de processos 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. Por outro lado, as substituições de manual -u <UID> persistem enquanto o UID
permanece ativo, até que todos os processos no UID sejam encerrados ou você limpe a substituição
com none.
Uma substituição manual não pode exceder os limites físicos do sistema.