No Android 17 e versões mais recentes, a plataforma controla a memória do processo do sistema usando dois mecanismos complementares do cgroup v2 do Linux que funcionam com o limitador de memória geral do aplicativo:
- Níveis de proteção do daemon do sistema (
SystemMemoryBudgetManagercommemory.min): as reservas dememory.mindo cgroup v2 com ajuste automático (definidas como 90% do conjunto de trabalho residente de cada daemon e limitadas globalmente a 10% da RAM física total) protegem daemons interativos críticos e processos de shell (system_server,surfaceflinger, o HAL de gráficoscomposer,audioserver,cameraserver,netd,SystemUIe o inicializador da página inicial) sob pressão de memória, substituindo a fixação de arquivos estáticosPinnerServicemlockestática. - Limites de memória do app do sistema (
<memory-budget>commemory.high): os limites flexíveis do cgroup v2memory.highcalibrados para carga de trabalho (normalmente de 30 MB a 190 MB) declarados em arquivos de manifesto para apps do sistema persistentes e em segundo plano (Bluetooth,Nfc,CalendarProvider,Traceur,MediaProvider,:PhotoPicker,NetworkStack,AppSearcheTeleService) recuperam proativamente o cache de páginas de arquivos frios e o heap anônimo pós-burst antes que ocorra pressão de memória global.
A tabela a seguir compara os dois mecanismos:
| Dimensão | Níveis de proteção do daemon do sistema | Limites de memória do app do sistema |
|---|---|---|
| Serviço de plataforma | SystemMemoryBudgetManager |
MemoryLimiter |
| Controle do Cgroup v2 | memory.min de piso de proteção rígido |
memory.high de limite de recuperação flexível |
| Processos de destino | Daemons interativos essenciais para latência e interface do shell (system_server, surfaceflinger, HAL composer, audioserver, cameraserver, netd, SystemUI e o app Home) |
Apps do sistema persistentes e headless, provedores de conteúdo e serviços em segundo plano (como Bluetooth, Nfc, MediaProvider, NetworkStack, AppSearch e TeleService) |
| Modelo de dimensionamento | Ajuste automático no ambiente de execução (90% do conjunto de trabalho estabelecido, limitado globalmente a 10% da RAM total) sem tabelas de ajuste por dispositivo | Atributos XML declarativos do manifesto <memory-budget> calibrados por nível de RAM, estado do processo e contagem de pixels da tela |
| Principal benefício | Evita instabilidade na interface interativa e travamentos de ativação sob pressão de memória ao substituir listas de arquivos estáticos PinnerService mlock |
Remove continuamente o cache de arquivos únicos frios e o heap pós-burst para deixar mais RAM física para apps em primeiro plano e em cache |
Para mais informações sobre a sintaxe de <memory-budget>, atributos XML, APIs de tempo de execução e contabilização de memória do SO, consulte Definir orçamentos de memória do app e Noções básicas e solução de problemas de memória do Android.
Proteger daemons interativos críticos com o SystemMemoryBudgetManager
O SystemMemoryBudgetManager mantém os processos que estão diretamente no caminho interativo do usuário
residentes na RAM física durante a pressão de memória. Em vez de
fixar uma lista estática de arquivos APK, ODEX e VDEX na memória com
mlock (como PinnerService faz para config_defaultPinnerServiceFiles e
config_pinnerHomePinBytes), o SystemMemoryBudgetManager atribui limites mínimos dinâmicos de cgroup
v2 memory.min aos seguintes processos:
- Fração do sistema principal (
/sys/fs/cgroup/system):system_server,surfaceflinger, a HAL de gráficoscomposer,audioserver,cameraserverenetd, além de qualquer sistema nativo específico do formato ou daemons de fornecedor listados emconfig_systemMemoryBudgetAdditionalSystemProcesses(por exemplo,android.hardware.perceptionhosteauracamerahalem dispositivos XR). - Fração de app interativa (
/sys/fs/cgroup/apps): o processo principal do pacote SystemUI configurado (config_systemUi) e o processo principal do usuário atual que tem a função de tela inicial.
Proteger o detentor da função de início junto com config_systemUi garante a cobertura
em todos os formatos. Em formatos em que config_systemUi é um
pacote de compatibilidade leve (como o Wear OS, em que o mostrador do relógio,
as notificações e os blocos são executados no app Home), o shell voltado ao usuário ainda
recebe proteção total do conjunto de trabalho. Se a intent Home for resolvida para o
pacote config_systemUi, o processo será protegido uma vez. Somente o processo principal
de cada pacote (processName == packageName) recebe um nível de proteção. Assim, os subprocessos auxiliares (como :screenshot) não consomem o orçamento de proteção.
Política de ajuste automático e delegação de cgroup em três níveis
Como o conjunto de trabalho de um daemon interativo varia muito entre formatos
e capacidades de RAM (por exemplo, o SystemUI usa cerca de 45 MB em um
smartwatch, 60 MB em um smartphone de 6 GB e 180 MB em um smartphone de
16 GB), as tabelas de consulta estáticas por dispositivo são frágeis. Em vez disso,
SystemMemoryBudgetManager deriva os limites diretamente do consumo residente de cada processo
usando três constantes:
| Parâmetro da política | Valor padrão | Comportamento |
|---|---|---|
| Preço mínimo por processo | 90% do memory.current liquidado menos o cache de arquivos não mapeados |
Protege o heap anônimo, o slab e o código mapeado quente, deixando os 10% mais frios recuperáveis para que as páginas genuinamente frias ainda possam ser removidas. O cache de arquivos não mapeados (max(0, file - shmem - file_mapped) de memory.stat) é subtraído antes de aplicar a proporção de 90% para que o cache de verificação de pacotes de inicialização ou as leituras de recursos únicos não inflem os pisos. |
| Limite global de RAM | 10% da RAM total (config_systemMemoryBudgetLowKb) |
Corresponde à proteção PinnerService config_pinnerMaxPinnedMemoryPercentage. Se a soma dos preços mínimos de 90% exceder 10% de MemTotal, todos os preços mínimos serão reduzidos proporcionalmente pelo mesmo fator. |
| Cadência de atualização | 1, 4, 16 e 64 s após o início e, em seguida, a cada 30 min | Uma série de estabilização geométrica converge no conjunto de trabalho pós-inicialização de cada daemon sem codificar atrasos de aquecimento por dispositivo, e uma atualização de estado estável de 30 minutos rastreia o crescimento e a redução gradual do conjunto de trabalho ao longo da vida útil do dispositivo. |
No controlador de memória cgroup v2 do Linux
(mm/memcontrol.c:effective_protection), a proteção memory.min efetiva de um cgroup de folha
é limitada pelo memory.min configurado nos cgroups pai
e ancestral. Definir memory.min apenas em um diretório folha pid_<pid>
enquanto uid_<uid>/memory.min é 0 resulta em zero proteção efetiva.
Por outro lado, definir um memory.min fixo apenas na fração /sys/fs/cgroup/system superior distribui a proteção não reivindicada entre todos os UIDs e processos filhos proporcionalmente (memory_recursiveprot), permitindo que processos de diagnóstico ou secundários que compartilham uid_1000 (android.uid.system) desviem a proteção de surfaceflinger e audioserver.
Para fornecer isolamento exato por daemon, o SystemMemoryBudgetManager grava
proteção em todos os três níveis da hierarquia do cgroup v2 em cada atualização,
conforme mostrado na figura 1:
- Nó do processo de folha (
pid_<pid>/memory.min): definido como o limite mínimo do conjunto de trabalho de 90% (escalonado) do processo. - Nó de UID (
uid_<uid>/memory.min): definido como a soma exata dos níveis de folhapid_*gerenciados, para que os processos não gerenciados que compartilham esse UID (como outros daemonsAID_SYSTEMou pacotessharedUserId="android.uid.system") tenhammemory.min = 0e não possam reivindicar proteção. - Fração de nível superior (
/sys/fs/cgroup/system/memory.mine/sys/fs/cgroup/apps/memory.min): definida como a soma exata dos filhosuid_*. Todos os valoresmemory.lowdesatualizados em nós gerenciados são limpos para0, e todos os diretóriosuid_*que foram gerenciados em uma atualização anterior (como um inicializador de início anterior depois que o usuário troca de inicializador) são redefinidos para0.
Figura 1. Delegação de piso de proteção de três níveis em fatias de cgroup.
Por que memory.min é usado em vez de memory.low em kernels MGLRU
Na LRU multigeracional (MGLRU, na sigla em inglês, o mecanismo de recuperação de página padrão no Android
6.6 e kernels mais recentes), memory.low e memory.min se comportam de maneira diferente em
mm/vmscan.c:shrink_one:
memory.low(adiamento de passagem única): um cgroup com uso abaixo dememory.lowé rotacionado para o final do LRU do memcg (MEMCG_LRU_TAIL) e ignorado em uma única passagem de recuperação. Na próxima passagem sob pressão de memória sustentada, o kernel recupera o cgroup por completo e registra um eventoMEMCG_LOW. Em testes de estresse de dispositivos físicos com um consumo de memória anônimo que chega a 85% da RAM física, omemory.lowpermitiu que oSystemUIem um smartwatch caísse de ~45 MB para ~5 a 10 MB, e oaudioserverem um dobrável de 16 GB caísse de 15 MB para 2 MB em comparação com um mínimo de ~14 MB.memory.min(piso de proteção rígido): um cgroup com uso abaixo dememory.miné ignorado pela recuperação global. Como mostrado na figura 2, sob a mesma carga de trabalho de pressão de RAM de 85%, omemory.mindescarta apenas os 10% mais frios de cada daemon e mantémSystemUI,surfaceflingereaudioserverem seus pisos de 90% com zero refaults anônimos e nenhum aumento nas finalizações de processolmkd.
O memory.min oferece uma reserva física rígida que corresponde à proteção da fixação do PinnerService mlock que ele substitui, com a vantagem de que o LRU do kernel (em vez de uma lista de arquivos estáticos) escolhe quais páginas de código, heap e gráficos quentes permanecem residentes.
Figura 2. Retenção do conjunto de trabalho do daemon sob pressão de memória crescente.
Pré-aquecimento de inicialização e recuperação de cache de arquivos não mapeados pós-inicialização
A substituição do mlock permanente de PinnerService de
config_defaultPinnerServiceFiles requer a coordenação de dois comportamentos
no momento da inicialização:
- Pré-aquecimento de inicialização em segundo plano único (
PinnerService): pularPinnerServicemlocksem aquecer o cache da página desloca cerca de 95 MB de leituras de disco de APK, ODEX e VDEX deSystemUIpara o caminho de inicialização síncrona quandoSystemUIé iniciado (OnBootPhase_550), adicionando de 300 a 450 ms à conclusão da inicialização. Chamarmadvise(MADV_WILLNEED)não é suficiente porqueforce_page_cache_rano kernel fixa uma única chamadaMADV_WILLNEEDa uma janela de leitura antecipada (normalmente de 128 KB a 2 MB). Em vez disso, quandoSystemMemoryBudgetManager.shouldSkipDefaultSystemPinningestá ativo,PinnerServicemapeia cada arquivo emconfig_defaultPinnerServiceFilesem uma linha de execução em segundo plano duranteonStart, preenche o arquivo de forma síncrona no cache de página comOs.mlocke chama imediatamenteOs.munlock,munmapeclose. Isso preserva o tempo de inicialização rápida, deixando zero bytes permanentemente bloqueados para que as páginas de inicialização a frio possam ser removidas mais tarde. - Exclusão e recuperação do cache de verificação de pacotes de inicialização do
system_server:durante a inicialização, oPackageManagerServiceverifica os APKs instalados, deixando de 1,5 a 2,5 GB de cache de página de arquivo não mapeado frio cobrado do cgroupsystem_server. Se incluído no cálculo dememory.mindesystem_server, esse cache de inicialização a frio satura o limite global de 10% de RAM e reduz proporcionalmente os limites de proteção desurfaceflinger,SystemUIeaudioserver. Para evitar a falta de limite, oSystemMemoryBudgetManagerlêmemory.statparasystem_server, calcula o cache de arquivos não mapeados comomax(0, file - shmem - file_mapped)e subtrai esse cache não mapeado dememory.currentantes de calcular o limite mínimo de 90%, conforme mostrado na Figura 3. Além disso, 10 segundos apósPHASE_BOOT_COMPLETED, o gerenciador realiza uma gravação única de<unmapped_file> swappiness=0no nómemory.reclaimdesystem_serverno Linux 6.2 e em kernels mais recentes para remover proativamente o cache frio de verificação de pacotes sem trocar o heap anônimo ou remover páginas de código e DEX mapeadas. Em kernels mais antigos (comoandroid14-6.1) que rejeitamswappiness=0comEINVAL, o gerenciador pula a gravaçãomemory.reclaimem vez de voltar para uma gravação de recuperação não qualificada que trocaria o heap anônimo durante a inicialização.
Figura 3. Convergência do piso de proteção e recuperação de cache após a inicialização.
Requisitos do kernel e sobreposições de configuração do dispositivo
O SystemMemoryBudgetManager depende de mem_cgroup_calculate_protection no
kernel do Linux para propagar memory.min em cgroups aninhados. Os kernels comuns do Android android15-6.6 e android16-6.12 incluem suporte completo à proteção aninhada do MGLRU. Os dispositivos que executam o android14-6.1 com o MGLRU
ativado exigem três backports upstream em mm/vmscan.c e mm/memcontrol.c
para que a fração intermediária vazia e os cgroups de UID propaguem a proteção para os cgroups de processo
de folha:
30d77b7eef01(mm/mglru: fix ineffective protection calculation)8aa420617918(mm/mglru: respect min_ttl_ms with memcgs)adb8213014b2(mm: memcg: fix stale protection of reclaim target memcg)
Os OEMs podem personalizar ou desativar o SystemMemoryBudgetManager usando as seguintes
sobreposições de recursos do framework:
config_systemMemoryBudgetLowKb(número inteiro, KB):0(padrão) limita a soma de todos os pisos dememory.mina 10% da RAM física total. Um valor positivo define um limite explícito em KB, e um valor negativo desativaSystemMemoryBudgetManagerno dispositivo (mantendo a fixação estáticamlocklegada dePinnerService).config_systemMemoryBudgetHighKb(número inteiro, KB):0(padrão) deixa/sys/fs/cgroup/system/memory.highemmax. Um valor positivo define um limite máximo de fração superiormemory.high(precisa ser maior ou igual ao limite mínimo).config_systemMemoryBudgetAdditionalSystemProcesses(matriz de strings): lista outros nomes de processos daemon de fornecedores ou sistemas nativos essenciais para a latência em/sys/fs/cgroup/system(correspondentes a/proc/<pid>/commou ao nome de base deargv[0]em/proc/<pid>/cmdlinepara UIDs de sistemas e fornecedores em10000) para proteger com limites de 90% dememory.min, além dos seis daemons padrão do sistema. Use essa sobreposição em vez de definirmemory.mineminit.rc, porqueSystemMemoryBudgetManagerrecalculauid_<uid>/memory.mincomo a soma dos processos de folha gerenciados e limpa os nósuid_*não gerenciados em cada atualização. Por exemplo, um dispositivo XR pode proteger os daemons de HAL de câmera e rastreamento espacial com a seguinte sobreposição:<string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses"> <item>android.hardware.perceptionhost</item> <item>auracamerahal</item> </string-array>config_systemUi(string): nome do pacote do app SystemUI da plataforma, resolvido comMATCH_SYSTEM_ONLY.
Para inspecionar os limites mínimos de SystemMemoryBudgetManager e os nós de cgroup ativos em um
dispositivo, execute:
# Inspect policy state, resolved PIDs, and applied floors in dumpsys meminfo adb shell dumpsys meminfo | sed -n '/System Memory Budgets/,$p'# Inspect raw cgroup v2 memory.min nodes across the system and apps slices adb shell head -n 1 \ /sys/fs/cgroup/system/memory.min \ /sys/fs/cgroup/system/uid_*/memory.min \ /sys/fs/cgroup/system/uid_*/pid_*/memory.min \ /sys/fs/cgroup/apps/memory.min \ /sys/fs/cgroup/apps/uid_*/memory.min \ /sys/fs/cgroup/apps/uid_*/pid_*/memory.min
Vincular apps do sistema persistentes e em segundo plano com orçamentos de memória
Enquanto o SystemMemoryBudgetManager protege daemons interativos contra a recuperação
usando limites de memory.min, muitos outros apps do sistema e serviços de plataforma
privilegiados são executados em segundo plano por longos períodos como processos PERSISTENT,
serviços em primeiro plano vinculados ou provedores de conteúdo consultados com frequência.
Sem limites de memória explícitos, esses processos não são restritos até que
alcancem os limites de vários gigabytes do limitador de memória (como 4 GB
a 10 GB para apps visíveis e 2 GB a 5 GB para apps em segundo
plano) ou até que a pressão de memória global acione a atividade do kswapd em todo o sistema e do
eliminador de pouca memória (lmkd).
Sem restrições, os processos do sistema em segundo plano geralmente acumulam grandes quantidades de memória fria que não faz parte do conjunto de trabalho ativo:
- Cache de página de arquivo ilimitado:serviços que indexam mídia, pesquisam bancos de dados
ou sincronizam tabelas de agenda e provedor (como
AppSearch,MediaProvidereCalendarProvider) mapeiam ou transmitem arquivos grandes no disco. Sem um limite de cgroupmemory.high, as páginas limpas com suporte de arquivo de leituras únicas permanecem carregadas no cgroup do processo indefinidamente, consumindo centenas de megabytes de RAM física. - Retenção de heap anônimo pós-burst:serviços que têm picos de atividade
transitórios, como bursts de áudio
Bluetoothe descoberta de BLE, validação de TLS de portal cativoNetworkStackou auxiliares de interface em transição para o segundo plano, podem reter páginas de heap anônimo frio muito tempo depois que o burst é concluído.
Aplicar declarações <memory-budget> calibradas (limites flexíveis de memory.high,
normalmente de 30 MB a 190 MB) a apps do sistema oferece dois benefícios
principais:
- Recuperação de página proativa e localizada:quando um processo do sistema ultrapassa o
orçamento de
memory.highconfigurado, o kernel do Linux direciona a recuperação de página especificamente para esse cgroup, removendo primeiro as páginas frias com backup de arquivos e compactando as páginas anônimas inativas para a ZRAM sem esperar pela pressão global da memória do dispositivo. - Mais RAM disponível para aplicativos em primeiro plano e o pool de apps em cache:vincular caches de arquivos frios em serviços do sistema sempre ativos recupera de dezenas a centenas de megabytes de RAM física em todo o dispositivo, reduzindo instabilidades na interface, diminuindo a pressão de inicialização da câmera e mantendo mais apps do usuário ativos no estado em cache.
Configurar orçamentos de memória de apps do sistema
Os apps do sistema usam a mesma sintaxe de manifesto <memory-budget>, cascata de estado do processo, escalonamento de densidade de exibição e regra de resolução de última cláusula correspondente que os apps gerais. Para ver a lista completa de
atributos, consulte a referência de atributos XML.
Os OEMs podem declarar elementos <memory-budget> diretamente no AndroidManifest.xml de um app do sistema ou mesclá-los de um arquivo XML separado usando a propriedade additional_manifests em Android.bp. Por convenção, o AOSP armazena
orçamentos de apps do sistema de referência em arquivos *-budget.xml independentes (em
frameworks/base/core/memory_budgets/ para pacotes de plataforma principal ou ao lado de
módulos individuais, como packages/modules/NetworkStack/memory_budgets/, packages/modules/AppSearch/apk/ e packages/services/Telephony/) e
os anexa com additional_manifests:
android_app {
name: "TeleService",
manifest: "AndroidManifest.xml",
additional_manifests: ["Telephony-budget.xml"],
// ...
}
Escopo no nível do pacote (UID) x no nível do processo (PID)
A declaração de um orçamento de referência no nível do pacote (<application>) ou de um orçamento de vários processos (<processes><process>) determina se o kernel aplica memory.high no nível do cgroup do UID (/sys/fs/cgroup/uid_<uid>/memory.high) ou no nível do cgroup por PID (/sys/fs/cgroup/uid_<uid>/pid_<pid>/memory.high), conforme mostrado na Figura 4:
- Orçamento no nível do pacote (
<application>):use quando o APK executar um único processo no próprio UID dedicado (por exemplo,Bluetooth,Nfc,NetworkStack,CalendarProvider,TraceurouAppSearch). - Orçamento no nível do processo (
<processes><process>):use quando o APK compartilhaandroid:sharedUserId(comoandroid.uid.phonecom UID1001ouandroid.uid.systemcom UID1000) com outros pacotes do sistema para que outros processos que compartilham esse UID não sejam limitados juntos ou quando um único APK executa vários processos com funções de memória distintas (por exemplo, o daemonMediaProviderem segundo plano e o subprocesso de interface:PhotoPicker).
Figura 4. Limites de memória no nível do UID em comparação com o nível do PID.
O exemplo a seguir mostra MediaProvider-budget.xml, que define o escopo dos orçamentos em camadas de RAM para o processo MediaProvider padrão (<process>) e um orçamento dimensionado para exibição para o processo de interface :PhotoPicker. Cada atributo android:maxDeviceRamMb corresponde a dispositivos com RAM física total (MemTotal) igual ou inferior ao limite de MB especificado (ou se aplica a qualquer capacidade de RAM quando omitido). As cláusulas são ordenadas do padrão sem restrições
até o menor limite de maxDeviceRamMb para que a
regra de resolução de última cláusula correspondente selecione
o nível aplicável mais restrito:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.providers.media.module">
<uses-sdk android:targetSdkVersion="10000" />
<application>
<processes>
<process>
<memory-budget
android:maxMb="85" />
<memory-budget
android:maxMb="75"
android:maxDeviceRamMb="12288" />
<memory-budget
android:maxMb="60"
android:maxDeviceRamMb="8192" />
</process>
<process android:process=":PhotoPicker">
<memory-budget
android:maxMb="85"
android:additionalBytesPerDisplayPixel="25" />
</process>
</processes>
</application>
</manifest>
Como os orçamentos de referência do AOSP foram calibrados em dispositivos Google Pixel
Os orçamentos de memória do app do sistema de referência no AOSP foram calibrados em dispositivos Google Pixel que abrangem de 6 GB a 16 GB de RAM física (incluindo smartphones, dobráveis e tablets de 6 GB, 8 GB, 12 GB e 16 GB) e resoluções de tela que variam de ~1, 2 MP (720×1600) a ~4, 5 MP (2076×2152).
A calibragem combinou duas fontes de dados complementares: telemetria de campo do cgroup v2 em toda a frota e varreduras de memory.high em dispositivos físicos controlados em jornadas ideais do usuário (CUJs, na sigla em inglês) críticas.
Análise de telemetria do cgroup v2 em toda a frota
Os snapshots periódicos do cgroup v2 foram coletados em toda a frota de dispositivos e divididos
por nível de RAM física (≤8 GB, 12 GB e ≥16 GB) e bucket de estado
do processo (foreground, perceptible, background e cached). Seis
princípios orientaram a análise estatística:
- Decompor
memory.currentem conjunto de trabalho versus cache de arquivos recuperáveis:comomemory.highcontrola a carga do cgroup residente (memory.current), cada snapshot foi decomposto usandomemory.statem heap anônimo (anon), páginas anônimas trocadas (swap) e cache de páginas com suporte de arquivo (file), como mostrado na Figura 5. A demanda anônima total incompressível (anon + swapno P99) estabelece o piso mínimo do conjunto de trabalho para o orçamento, de modo que o processo não corra o risco de esgotar a ZRAM, enquanto uma grande lacuna entre o P99 dememory.currente o P99 deanon + swapindica o cache de página de arquivo que omemory.highpode recuperar, desde que o cache esteja desmapeado e frio. - Considere a memória que o
memory.highnão pode recuperar:nem toda a memória de arquivos e compartilhada é cache frio. As páginas bloqueadas (unevictableemmemory.stat, como a memória compartilhada bloqueada commlockou a RAM de convidado bloqueada de uma máquina virtual com processos de host cobrados no cgroup do app) definem um limite rígido abaixo do orçamento, e as páginas mapeadas (file_mapped) geralmente fazem parte do conjunto de trabalho ativo. Verifiqueunevictable,shmemefile_mappedantes de tratar a memória de arquivos como recuperável. Por exemplo, cerca de 100 MB de memóriaAppSearchestão bloqueados e não podem ser removidos. Portanto, o orçamento cobre esse limite mínimo e o conjunto de trabalho do host ativo, com espaço para que a região bloqueada cresça. - Filtrar a diluição do estado inativo:os provedores de conteúdo (
CalendarProvider) e as ferramentas para desenvolvedores sob demanda (Traceur) passam de 70% a 99,9% dos snapshots de campo inativos em cerca de 1 MB a 3 MB. O cálculo de percentis brutos em todos os snapshots dilui severamente as estimativas de P95 e P99. Em vez disso, os percentis e os intervalos de confiança foram calculados com base em snapshots ativos não diluídos (filtrando snapshots deCACHEDe snapshots de referência inativos). - Use intervalos de confiança agrupados por dispositivo:como um único dispositivo contribui com muitos snapshots periódicos ao longo do tempo, tratar os snapshots como amostras independentes subestima a incerteza da cauda. Todas as estimativas de conjunto de trabalho P95 e P99 foram avaliadas usando intervalos de confiança conservadores de 95% agrupados por dispositivo (com a contagem de dispositivos distintos
N_devcomo o tamanho da amostra efetiva). - Considere picos temporários com
memory.peak:a amostragem periódica pode não detectar picos de curta duração e alta alocação, como a validação de TLS de portal cativoNetworkStacke sondagens de DNS simultâneas ou a inicialização de A2DP e BLEBluetooth. Para serviços com picos de uso, os orçamentos foram dimensionados para cobrir do 90º ao 95º percentil de marcas d'água altas do kernelmemory.peakdurante o ciclo de vida em todos os níveis de RAM. - Confirme se um nível de RAM reflete a RAM, não a geração do dispositivo:dispositivos
mais recentes tendem a ter mais RAM e mais recursos. Portanto, um conjunto de trabalho que
aumenta com o nível de RAM pode estar aumentando com os recursos. Antes de adicionar
um nível
maxDeviceRamMb, divida os dados por modelo de dispositivo. Por exemplo, ocom.android.phonealcança o mesmo P99anon + swap(cerca de 85 a 90 MB) em smartphones de 12 GB e 16 GB da mesma geração, enquanto um dobrável mais antigo de 12 GB usa 40 MB. Portanto, o orçamento usa um único valor sem nível de RAM, para que um dispositivo com menos RAM que forneça os recursos de telefonia mais recentes não tenha um orçamento insuficiente.
Figura 5. Conjunto de trabalho versus cache de arquivos recuperável.
CUJ de dispositivo físico e varreduras de dimensionamento de tela
Para validar os orçamentos derivados de telemetria e calibrar os orçamentos proporcionais à tela (como :PhotoPicker) e os orçamentos em segundo plano específicos do estado (como Traceur), foram executadas varreduras de memory.high em tempo real em dispositivos físicos com 6 GB a 16 GB de RAM:
- Como encontrar o ponto de inflexão de zero refault:ao executar CUJs de ponta a ponta (como
rolagem rápida de grade de fotos, navegação em álbuns, deslizar para pré-visualizar fotos e vídeos em alta resolução
em tela cheia e gravação e salvamento de rastros),
memory.highfoi reduzido ao medir os contadoresmemory.statdo cgroup (workingset_refault_anon,workingset_refault_file,pgscanepgsteal) ememory.events(high). Como ilustrado na figura 6, o orçamento ideal fica logo acima do ponto de inflexão de zero refault. O ponto de inflexão de zero refault é o limite em que o kernel recupera o cache de arquivos único frio (pgsteal > 0) sem acionar o thrashing do conjunto de trabalho ativo (workingset_refault == 0). - Validação do dimensionamento de pixels de tela em várias resoluções:testes do
:PhotoPickerem quatro resoluções de tela físicas e emuladas, de ~1,2 MP (720 × 1600) a ~4,5 MP (tela interna desdobrada de 2076 × 2152), confirmaram que o conjunto de trabalho de bitmap ativo do Glide e do ViewPager2 é dimensionado linearmente com a contagem de pixels da tela. A referência sem exibição também varia de acordo com o dispositivo: com quase a mesma contagem de pixels, o ponto de inflexão de zero falhas de página foi de cerca de 140 MB na tela externa de um dispositivo dobrável de 16 GB, em comparação com 110 a 125 MB em um smartphone de 6 GB. Uma linha de base de 85 MB mais 25 bytes por pixel de tela (android:maxMb="85"eandroid:additionalBytesPerDisplayPixel="25") permanece acima do ponto de inflexão medido em todas as telas testadas (~110 MB em HD+, ~145 MB em FHD+ e ~190 MB em telas abertas). - Verificação da coleta de lixo do ART em estados em segundo plano:no cgroup v2,
as páginas de imagem do framework e do
boot.artcompartilhadas pelo Zygote permanecem cobradas do cgroup do Zygote e são tratadas como um espaço imune durante a coleta de lixo de marcação e compactação simultânea (CMC, na sigla em inglês) do ART. Os testes de estresse confirmaram que, quando um app de interface do sistema faz a transição parabackground, o aperto dememory.high(por exemplo, para 30 MB emTraceur) limita de forma limpa o heap privado e o cache de páginas sem causar thrashing de GC ou atividade de troca.
Figura 6. Encontrar o ponto de inflexão de zero falha de página durante uma CUJ.
Resumo dos orçamentos de apps do sistema de referência do AOSP
A tabela a seguir resume os orçamentos de referência calibrados no AOSP em todos os níveis de RAM e estados de processo:
| Pacote ou processo do sistema | Escopo | ≤8 GB de RAM | 12 GB de RAM | ≥16 GB de RAM | Driver de calibragem principal |
|---|---|---|---|---|---|
com.android.bluetooth |
UID (<application>) |
80 MB | 95 MB | 110 MB | Abrange picos de memory.peak de ciclo de vida P90 (cerca de 75 a 110 MB) e >2x memory.current ativo P99. |
com.android.nfc |
UID (<application>) |
50 MB | 60 MB | 60 MB | Abrange a emulação de cartão host ativa P99 (até ~50 MB) e P90 a P95 memory.peak ao cortar o cache de arquivos frios. |
com.android.providers.calendar |
UID (<application>) |
70 MB padrão (primeiro plano) 50 MB perceptible e background |
O conjunto de trabalho ativo é invariável em todos os níveis de RAM. O orçamento em segundo plano mantém uma margem anônima para alarmes de lembrete enquanto reduz o cache de páginas do SQLite durante as sincronizações. | ||
com.android.traceur |
UID (<application>) |
85 MB padrão (primeiro plano e perceptível) 30 MB background |
Preserva o espaço de 85 MB durante a gravação e exportação de rastreamento ativo (TraceService), limitando o estado inativo e de transmissão a 30 MB. |
||
com.android.providers.media.module (principal) |
PID (<process>) |
60 MB | 75 MB | 85 MB | Oferece cerca de 10 a 30 MB de espaço livre em relação ao P99 anon + swap ao recuperar o SQLite frio e o cache de arquivos de miniatura. |
com.android.providers.media.module:PhotoPicker |
PID (<process>) |
85 MB + 25 bytes por pixel (~110 MB em HD+, ~145 MB em FHD+, ~190 MB em dobra de ~4,5 MP) |
É dimensionado proporcionalmente com a contagem de pixels da tela (additionalBytesPerDisplayPixel="25") para corresponder ao limite do conjunto de trabalho de bitmap do Glide. |
||
com.android.networkstack |
UID (<application>) |
65 MB | 70 MB | 85 MB | Memória predominantemente anônima, dimensionada para o ciclo de vida P95 memory.peak para cobrir picos de TLS, DNS e DHCP do portal cativo. |
com.android.appsearch.apk |
UID (<application>) |
130 MB | Abrange cerca de 100 MB de memória bloqueada e não removível, além de um conjunto de trabalho de host ativo de cerca de 5 MB, com espaço para a região bloqueada crescer. Zero falhas de página medidas em ≥125 MB enquanto o cache de página fria (até ~330 MB em P99) é recuperado. | ||
com.android.phone (TeleService) |
PID (<process>) |
120 MB | Com escopo por PID no UID compartilhado 1001. O conjunto de trabalho rastreia a geração do dispositivo em vez da RAM. Portanto, um único orçamento fica cerca de 30 MB acima do P99 anon + swap mais alto por modelo. |
||
Como os OEMs podem recalibrar as cotas de apps do sistema para os próprios dispositivos
Os OEMs que personalizam apps do sistema (por exemplo, adicionando perfis de Bluetooth do fornecedor,
extensões de telefonia personalizadas ou indexadores de mídia do OEM) ou que enviam serviços do sistema
pré-instalados adicionais podem recalibrar os orçamentos atuais ou criar novos arquivos
*-budget.xml para o portfólio de hardware.
Coletar perfis de memória ativos do cgroup v2
Para cada processo do sistema de destino, leia os nós de memória do cgroup v2 em
/sys/fs/cgroup/uid_<uid>/pid_<pid>/ em cargas de trabalho representativas e níveis de
RAM:
memory.current: memória residente atual cobrada do cgroup (exceto páginas compartilhadas do Zygote)memory.peak: pico de uso dememory.currentdesde o início do processo.memory.swap.current: uso atual de troca compactada na ZRAM.memory.stat: detalhamento deanon,file,pgscan,pgsteal,workingset_refault_anoneworkingset_refault_filememory.events: contagem cumulativa de violações dehigh(eventos de limitação e recuperação dememory.high)
Ao agregar telemetria de dispositivos de dogfood ou de campo, separe os estados de processo
ativos (TOP, FOREGROUND_SERVICE, vinculações ativas e sincronizações ativas)
dos estados CACHED ou PERSISTENT inativos para que os snapshots inativos não distorçam os
cálculos do conjunto de trabalho P95 e P99 para baixo.
Separar heap não compactável do cache de arquivos recuperável
Compare o 99º percentil de anon + swap com o 99º percentil de
memory.current:
- Serviços dominados por cache de arquivos (
file≫anon, como provedores de banco de dados, indexadores de pesquisa e daemons de registro): definaandroid:maxMbpara fornecer uma margem confortável (normalmente de 1,5x a 4x) em relação ao P99anon + swap, além de uma janela de trabalho para páginas de arquivos quentes. Essa configuração permite que o kernel remova continuamente páginas de arquivos frios não mapeados sem tocar no heap anônimo. Seunevictableoufile_mappedfor grande (como com memória bloqueada ou bancos de dados mapeados na memória), conte essa pegada como conjunto de trabalho também. - Serviços dominados por heap anônimo ou com picos (
anoncompõe a maior parte dememory.current, como pilhas de rede ou conectividade): dimensioneandroid:maxMbpara cobrir de P90 a P95 dememory.peakpara que picos transitórios não acionem a troca síncrona de ZRAM em linhas de execução sensíveis à latência. - Processos de interface com uso intensivo de tela (como seletores, galerias ou auxiliares
de interface do sistema): meça o consumo de memória de referência que não é de interface e o consumo de memória de bitmap
incremental por megapixel nas telas de menor e maior resolução
e expresse o orçamento usando
android:maxMbmaisandroid:additionalBytesPerDisplayPixel.
Limpar memory.high em dispositivos físicos
Antes de confirmar um novo orçamento para o build, use am memory-limiter manual (ou
grave diretamente no nó do cgroup memory.high do processo em um build userdebug)
para testar os limites candidatos ao executar os testes automatizados de CUJ:
# Override memory.high for a specific PID to 60 MB during a CUJ test
adb shell am memory-limiter manual <PID> 60MBMonitore memory.stat e memory.events antes e depois da CUJ:
- O orçamento é muito baixo:
workingset_refault_anonouworkingset_refault_fileaumenta drasticamente durante a CUJ,memory.swap.currentcresce continuamente durante o trabalho ativo em primeiro plano ou a latência do frame da CUJ ou do Binder regride. Aumente o orçamento além do ponto de inflexão de falha. - O orçamento é muito alto:
memory.events:highpermanece0mesmo após cargas de trabalho pesadas de streaming de arquivos ou indexação, efileemmemory.statcresce sem limites com páginas frias. Reduza o orçamento para o conjunto de trabalho ativo. - O orçamento está bem calibrado:o
memory.currentem estado estável permanece abaixo dememory.highdurante a operação normal. Durante picos de arquivos ou cache, opgstealrecupera páginas de arquivos frios, enquanto oworkingset_refault_anonpermanece perto de zero e a latência de CUJ não muda.