Quando você precisar investigar o uso da memória, comece com ferramentas de avaliação de alto nível para identificar quais categorias de memória são mais significativas.
dumpsys meminfo
A maneira mais comum de ter uma visão geral do uso da memória de um aplicativo é
dumpsys meminfo. Para ver um detalhamento que inclui todos os mapeamentos de memória, costuma ser útil incluir a flag -a:
adb shell dumpsys meminfo -a <package_name_or_pid>
Confira um exemplo de saída de um aplicativo sob alguma pressão de memória (observe o alto SwapPss):
** MEMINFO in pid 4562 [com.android.memorylab] **
Pss Private Private SwapPss Rss Heap Heap Heap
Total Dirty Clean Dirty Total Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------
Native Heap 394 268 64 17440 4264 29416 18128 8040
Dalvik Heap 165 8 0 672 8196 40949 32757 8192
Dalvik Other 47 28 0 604 1132
Stack 112 108 4 312 120
...
TOTAL 33197 580 788 21920 167776 70365 50885 16232
App Summary
Pss(KB) Rss(KB)
------ ------
Java Heap: 12 32608
Native Heap: 268 4264
Code: 724 125672
Stack: 108 120
Graphics: 0 0
Private Other: 256
System: 31829
Unknown: 5112
TOTAL PSS: 33197 TOTAL RSS: 167776 TOTAL SWAP PSS: 21920
Objects
Views: 15 ViewRootImpl: 1
AppContexts: 5 Activities: 1
...
Interpretação dos dados
- Pss Total: o tamanho do conjunto proporcional. É a soma de
Private DirtyPrivate Clean+ a parcela equitativa de memória compartilhada com outros processos (como a imagem de inicialização do Zigoto). Essa é a melhor métrica para "quanta memória esse app é responsável".
- Particular e suja: é a RAM que apenas seu processo está usando e que foi modificada (ou é uma memória anônima). Esse é o número mais crítico para encontrar vazamentos, porque essa memória não pode ser descartada.
- Particular e limpa: RAM que apenas seu processo está usando, mas que é uma cópia não modificada de um arquivo no armazenamento (como um arquivo DEX). Ele pode ser descartado pelo SO se a memória estiver baixa.
- SwapPss: memória que foi compactada e trocada para a ZRAM. Isso pode
acontecer porque o sistema está sob pressão de memória ou porque o
Android Runtime identificou processos em segundo plano para compactar (movendo as
páginas não utilizadas ou inativas para a ZRAM). No exemplo de
dumpsys, quase todos osNative HeapeDalvik Heapforam movidos para a ZRAM.
- Resumo do app: uma categorização de nível mais alto que combina as linhas detalhadas em buckets compreensíveis.
- Objects: útil para rastrear instâncias vazadas de
ActivitieseViewRootImpl. Se você sair do app e forçar uma GC, a contagem deActivitiesvai voltar para 0.
Seção de orçamento de memória
No Android 18 e versões mais recentes, o dumpsys meminfo também mostra uma seção de orçamento de memória
ao despejar um único app. Essa seção lê as estatísticas do memcg
do processo e de todo o app (o pacote), para que
você não precise encontrar os arquivos do cgroup por conta própria. Esta é a seção do
app de exemplo MemoryLab em primeiro plano, depois de três toques em Allocate Java
Memory (10MB):
Memory Budget
Process(KB) Package(KB)
----------- -----------
Current Usage: 83820 87976
Limit: none 4718592
Limit Source: none MemoryLimiter
Peak Usage: 85136 89292
Swap Used: 0 0
Usage Breakdown
Anonymous: 81700 81700
File: 96 4200
File Mapped: 16 616
Shared Memory: 16 16
Kernel: 1580 1632
Page Tables: 640 640
Slab: 439 458
Pressure & Reclaim
Throttle Events: 0 0
Refaults (Anon): 0 0
Refaults (File): 0 0
- Uso atual: memória cobrada do memcg (
memory.current). Esse é o número que os orçamentos de memória limitam. No exemplo, ele aumentou em cerca de 36.700 KB após as três alocações de Java de 10 MB, de acordo com o TOTAL PSS. O total do pacote é maior que o total do processo, mesmo para um app de processo único, quando as páginas de arquivos em cache de uma execução anterior ainda não foram reivindicadas. - Limite: o limite de recuperação (
memory.high) que se aplica no momento ounone, se não houver um. No exemplo, o pacote tem um limite de memória do sistema, e o processo não tem um limite próprio. - Origem do limite: de onde vem o limite.
AndroidManifesté um<memory-budget>declarado no manifesto do app,MemoryBudgetManageré um orçamento definido pelo app no momento da execução,MemoryLimiteré um limite de memória do sistema eam memory-limiteré uma substituição manual definida comadb. - Uso de pico: a maior cobrança desde que o memcg foi criado
(
memory.peak). Isso mostraN/Aem kernels que não são compatíveis. - Troca usada: memória compactada em ZRAM (
memory.swap.current). - Detalhamento de uso: os campos principais de
memory.stat, como memória anônima e com backup de arquivos. - Pressão e recuperação: os eventos de limitação contam quantas vezes o memcg
ultrapassou o limite de
memory.high. Refaults conta as páginas que foram trocadas ou removidas e depois foram necessárias novamente. Se esses números aumentarem enquanto você usa o app, o limite é mais restrito do que o conjunto de trabalho do app.
Com a flag -a ou -v, a seção também lista todos os contadores brutos de
memory.stat e memory.events.
Exercício prático: triagem rápida com meminfo
Este exercício mostra como diferentes tipos de alocações aparecem no meminfo.
Inicie o app:
adb shell am start -W -n com.android.memorylab/.MainActivityVerificar a memória de referência:
adb shell dumpsys meminfo -s com.android.memorylabA flag
-sfornece um resumo curto e conciso das categorias de memória.Allocate Java Memory: toque no botão Allocate Java Memory (10MB) três vezes no app.
Observe a mudança: execute
meminfo -snovamente. O valor do heap Java vai aumentar em aproximadamente 30.000 KB (30 MB).Alocar memória nativa: toque no botão Alocar memória nativa (1 GB não compactável) uma vez.
Observe a mudança: execute
meminfo -snovamente. O valor de Native Heap agora vai mostrar um aumento enorme (~1.000.000 KB).Comparar com o memcg (Android 18 e versões mais recentes): consulte a seção Memory Budget no final da saída de
meminfo -s. O Uso atual aumenta junto com o PSS TOTAL após cada alocação, porque as alocações Java e nativas são cobradas do memcg do app.
Visualização global
Para ver o uso da memória em todo o sistema, execute:
adb shell dumpsys meminfo
Isso fornece um resumo do uso de RAM, incluindo a RAM total, a RAM livre e uma lista de processos classificados por PSS.
Principais métricas do sistema
- ZRAM: troca compactada na RAM.
- DMA-BUF: buffers de memória compartilhada e gráficos. As versões modernas do Android usam
o DMA-BUF extensivamente para renderização de interface. Se o total de
DMA-BUFfor muito alto, investigue processos com muitos gráficos usando/sys/kernel/debug/dma_buf/bufinfo. - RAM perdida: memória que não é contabilizada pelo kernel ou por qualquer processo (por exemplo, regiões reservadas para hardware).
Estatísticas de memcg
Cada processo de app tem o próprio grupo de controle de memória (memcg), e o kernel mantém os totais de memória contabilizados nele (consulte memcg para saber como essa contabilização difere de RSS e PSS). A leitura desses arquivos é barata e não interfere no app. Portanto, é possível fazer uma amostragem deles a cada segundo enquanto você executa um fluxo do usuário.
Procure o caminho memcg de um processo na linha 0:: de /proc/<PID>/cgroup,
em vez de criar o caminho por conta própria, porque o layout do cgroup pode ser diferente
entre os dispositivos. Transmita cada comando do dispositivo para adb shell como uma única string entre aspas para que o shell do dispositivo veja as aspas:
PID=$(adb shell pidof -s <package_name>)
CG=/sys/fs/cgroup$(adb shell "sed -n 's/^0:://p' /proc/$PID/cgroup")
adb shell "cat $CG/memory.current $CG/memory.swap.current"
adb shell "grep -E '^(anon|file|workingset_refault_.*) ' $CG/memory.stat"
Os arquivos principais são:
memory.current: memória cobrada do processo, em bytes. Esse é o número que os orçamentos de memória limitam.memory.swap.current: memória compactada em ZRAM, em bytes.memory.stat: um detalhamento da cobrança, em bytes para tamanhos.anoné memória anônima, como os heaps Java e nativo.fileé o cache de página com suporte de arquivo, como código e recursos.workingset_refault_anoneworkingset_refault_filecontam as páginas que foram trocadas ou removidas e que foram necessárias novamente. O aumento da contagem de refaults significa que o processo está com pouca memória.memory.events: contagens de eventos de limite de memória.highconta quantas vezes o processo foi limitado por ultrapassar o limite dememory.high.
O diretório pai do memcg do processo pertence ao app inteiro (o UID dele). Os arquivos dele incluem a memória de todos os processos em execução do app, além de páginas de arquivos em cache que sobraram de processos anteriores do app que foram encerrados e ainda não foram recuperados:
adb shell "cat $CG/../memory.current"
Se você receber um erro de permissão, execute adb root e tente novamente. Alguns dispositivos só
permitem ler esses arquivos como raiz.
Para um tutorial guiado desses arquivos com o app de exemplo MemoryLab, consulte Exercício prático: como explorar o memcg.
showmap
Para uma análise muito mais detalhada dos mapeamentos de memória de um processo, use
showmap. Essa ferramenta lê de /proc/<pid>/smaps.
adb shell showmap <pid>
showmap lista todos os mapas de memória no processo. Os principais mapeamentos a serem procurados
incluem:
[anon:scudo:primary]ou[anon:libc_malloc]: o heap nativo. As versões modernas do Android usam o alocador Scudo.*.art: imagens de heap do Java (por exemplo, imagem de inicialização).[anon:dalvik-main space]: o heap principal do Java.*.so: bibliotecas compartilhadas.*.{oat,dex,odex,vdex}: código DEX compilado.
É possível classificar a saída para encontrar os maiores consumidores de memória particular:
# Sort by private clean + private dirty memory
adb shell showmap <pid> | awk '{print $6+$7, $0}' | sort -rn | head -n 10
Exemplo de resposta:
virtual shared shared private private Anon Shmem File Shared Private
size RSS PSS clean dirty clean dirty swap swapPSS HugePages PmdMapped PmdMapped Hugetlb Hugetlb Locked # object
-------- -------- -------- -------- -------- -------- -------- -------- -------- --------- --------- --------- -------- -------- -------- ---- ------------------------------
2776 18176 5784 2823 0 3008 308 2468 4700 4700 0 0 0 0 0 0 47 [anon:scudo:primary]
560 952 876 657 316 0 560 0 12 12 0 0 0 0 0 0 8 /data/app/.../base.apk
448 393216 448 448 0 0 0 448 0 0 0 0 0 0 0 0 2 [anon:dalvik-main space]
...
Exercício prático: investigação com o showmap
Aprenda a identificar mapeamentos de memória específicos e diferenciar entre memória limpa e suja.
- Mmap Clean Memory: toque no botão Mmap Clean Anon Memory (10MB) no app MemoryLab uma vez.
Receber o ID de processo: a interface do app mostra o ID de processo. Também é possível encontrá-lo com este comando:
adb shell pidof com.android.memorylabInspecionar com showmap:
# Replace <PID> with the actual process ID from the previous step adb shell showmap <PID> | grep "memorylab-clean"Identifique o mapeamento: você verá um mapeamento anônimo rotulado como
[anon:memorylab-clean]com um tamanho virtual de exatamente 10.240 KB. O valor private dirty para esse mapa provavelmente é 0. Essa é uma memória anônima "limpa" porque usamosmmappara reservar o espaço virtual, mas ainda não gravamos nele. Portanto, nenhuma RAM física é realmente consumida.Comparação de heap nativo: compare isso com os mapeamentos de
[anon:scudo:primary], que teriam aparecido emshowmapse você tivesse executado após a etapa 5 do exercício dememinfo. Como essa alocação nativa de 1 GB grava em todas as páginas, elas são "sujas" e aparecem em grande quantidade na coluna private dirty.Dica profissional (objetos grandes): observe as alocações de heap Java do exercício
meminfo. Como são matrizes de bytes de 10 MB, elas provavelmente aparecem emshowmapcomo[anon:dalvik-free list large object space]. O Android Runtime (ART) coloca objetos maiores que 12 KB em um espaço de objeto grande (LOS, na sigla em inglês) separado para evitar a fragmentação no heap principal.
procstats
Enquanto o meminfo fornece um snapshot em um momento específico, o procstats fornece dados históricos
ao analisar periodicamente o estado e o uso de memória de todos os aplicativos
em execução. Essa é a principal ferramenta usada para monitorar a integridade da memória de longo prazo
e é o que impulsiona os relatórios de uso da memória nas Configurações do Android.
# View stats for the last 3 hours
adb shell dumpsys procstats --hours 3
Noções básicas sobre os estados
A saída de procstats pode ser muito grande. A parte mais importante são as
métricas entre parênteses ao lado de cada estado do processo:
Top: 100% (54MB-64MB-67MB / 49MB-58MB-61MB / 110MB-120MB-130MB over 4)
O formato é: (% de tempo no estado) (minPss-avgPss-maxPss / minUss-avgUss-maxUss / minRss-avgRss-maxRss em amostras)
- Pss (Proportional Set Size): a memória compartilhada é dividida proporcionalmente entre os processos.
- Uss (Unique Set Size): memória usada apenas por esse processo (equivalente a Private Clean + Private Dirty).
- Rss (Resident Set Size): memória física total usada, incluindo todas as páginas compartilhadas (tende a contar em excesso).
Por que isso é importante:
- App estável: os valores mínimo, médio e máximo estão próximos.
- Vazamento de memória: o máximo é significativamente maior que o mínimo, e a média aumenta ao longo do tempo conforme a contagem de amostras aumenta.
- Uso com picos: o máximo é muito maior que a média, indicando operações pesadas temporárias (como processamento de imagens) que podem causar OOMs se acontecerem no momento errado.
Estados do processo
O procstats rastreia apps em vários estados (geralmente abreviados):
- Persist: processos críticos do sistema que estão sempre em execução.
- Parte de cima: o app está em primeiro plano e visível para o usuário.
- ImpFg (primeiro plano importante): processos críticos em segundo plano (como IMEs) ou apps com um serviço em primeiro plano.
- ImpBg (segundo plano importante): processos em segundo plano que são considerados importantes para o sistema.
- Fgs / BFgs: serviços em primeiro plano ou serviços em primeiro plano vinculados.
- Service / ServRst: serviços em segundo plano ou serviços que estão sendo reiniciados.
- Em cache / LastAct: apps que estão em cache em segundo plano para reinicialização rápida. O sistema pode encerrar esses processos com segurança.
Estado da memória do dispositivo e estatísticas de tempo de execução
O procstats também monitora quanto tempo o dispositivo inteiro passou sob pressão de
memória. Procure a seção Estatísticas de tempo de execução:
Run time Stats:
SOff/Norm: +1h10m15s
SOn/Norm: +15m20s
Low: +2m10s
Crit: +45s
- SOff / SOn: tela desligada x tela ligada.
- Norm / Mod / Low / Crit: níveis de pressão de memória (Normal, Moderado, Baixo, Crítico).
Se um dispositivo passar muito tempo nos estados Low ou Crit, ele vai parecer lento, já que o kernel vai recuperar a memória de forma agressiva e o Low Memory Killer (LMK) vai começar a encerrar os processos em segundo plano. Em seguida, analise as estatísticas por pacote para ver quais apps têm alto uso da memória especificamente quando o dispositivo está nesses estados de estresse.
← Conceitos | ↑ Voltar | Java →