Nas seções anteriores, vimos como o kernel gerencia a memória em todo o sistema. Neste capítulo, vamos nos aprofundar nos grupos de controle de memória (memcg), o mecanismo que o Android usa para particionar e controlar o uso da memória em apps individuais.
Entender o memcg é crucial porque é o nível em que o sistema pode segmentar apps individuais para recuperação ou definir limites de memória neles, permitindo o gerenciamento proativo do consumo de memória dos apps.
Grupos de controle de memória (memcg)
Os grupos de controle (cgroups) são um recurso do kernel do Linux que permite organizar processos em grupos hierárquicos e distribuir recursos do sistema (como CPU, memória e E/S) entre eles. memcg é o controlador de cgroup especificamente para memória.
Principais conceitos de memcg
- Cobrança do memcg: quando um processo em um memcg aloca uma página de memória (anônima ou com suporte de arquivo), essa página é "cobrada" do memcg. A carga total de um memcg é a soma de todas as páginas usadas por todos os processos nele.
- Contabilidade hierárquica: o uso da memória é contabilizado na árvore. Uma cobrança em um cgroup filho também conta para o uso do pai.
- Limites de memória: cada memcg oferece suporte a um limite de recuperação flexível usando
memory.high, que os orçamentos de memória do app usam para acionar a recuperação direcionada e a limitação de alocação sem invocar o eliminador de OOM. Ele também é compatível com um limite rígido por meio dememory.max, que os limites de memória do sistema usam para encerrar processos descontrolados quando a recuperação não consegue manter o uso abaixo do limite. - Recuperação por memcg: quando um memcg ultrapassa o limite ou quando o sistema precisa de memória, o kernel pode segmentar um memcg específico para recuperação. Isso significa remover as páginas de arquivo ou trocar as páginas anônimas para a ZRAM.
A hierarquia memcg do Android
O Android usa uma hierarquia específica para gerenciar processos de apps. Essa estrutura permite que o sistema aplique políticas diferentes a tipos diferentes de apps (por exemplo, em primeiro plano x em segundo plano).

/sys/fs/cgroup/apps/: a raiz de todos os apps Android.uid_<UID>/: um diretório para o ID do usuário de cada app. Todos os processos que pertencem ao mesmo pacote do app compartilham esse grupo.pid_<PID>/: um diretório para cada processo individual e todos os filhos bifurcados dele. Isso permite um controle e uma contabilização refinados para apps com vários processos.
Exercício prático: como explorar o memcg
Neste exercício, você vai encontrar o diretório memcg do MemoryLab e observar a carga de memória em tempo real.
1. Iniciar o MemoryLab
Verifique se o MemoryLab está em execução no seu dispositivo.
2. Encontrar o memcg do MemoryLab
Primeiro, encontre o PID do app MemoryLab em execução:
adb shell pidof com.android.memorylab
# Example output: 11672
Agora, localize o diretório cgroup dele. Você pode encontrar isso em /proc/<PID>/cgroup:
adb shell cat /proc/11672/cgroup
# Example output: 0::/apps/uid_10274/pid_11672
No cgroup v2, o caminho após 0:: representa a hierarquia memcg em relação
a /sys/fs/cgroup. Portanto, o caminho completo é
/sys/fs/cgroup/apps/uid_10274/pid_11672/.
3. Ler estatísticas de memcg
Acesse esse diretório e confira os arquivos de chave:
# Current memory usage (in bytes)
adb shell cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.current
# Example output:
# 1591324672
memory.current mostra a quantidade total de memória (em bytes) atualmente cobrada
desse cgroup.
# Detailed statistics
adb shell cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.stat | head -n 10
# Example output:
# anon 1585098752
# file 741376
# kernel 5484544
# kernel_stack 393216
# pagetables 4583424
# sec_pagetables 0
# percpu 216
# sock 0
# vmalloc 4096
# shmem 20480
memory.stat fornece um detalhamento: * anon: quantidade de memória anônima
(heaps, pilhas). * file: quantidade de memória com backup de arquivos (cache de página). *
swap: quantidade de memória trocada para a ZRAM.
4. Observar mudanças
- Abra o MemoryLab.
- Anote o valor de
memory.current. - Toque em Allocate Java Memory (10MB) várias vezes.
- Leia
memory.currentnovamente. Você vai notar um aumento de aproximadamente 10 MB para cada toque. - Toque em Allocate Bitmaps. Confira
memory.statpara ver o aumento deanon.
Recuperação proativa com memory.reclaim
Um recurso avançado do memcg (v2) é o arquivo memory.reclaim. Gravar um valor
nesse arquivo instrui o kernel a tentar recuperar imediatamente essa quantidade
de memória desse memcg e de qualquer memcg abaixo dele.
Como o Android usa o memory.reclaim
O CachedAppOptimizer do Android usa esse recurso para recuperar o máximo de memória
dos apps depois que eles são congelados. O App Freezer do Android garante que
os apps em cache consumam o mínimo de RAM possível enquanto não estiverem em execução. Quando um app é movido para o segundo plano e congelado, o sistema grava o uso da memória atual do app no arquivo memory.reclaim. Isso força o kernel a
remover todas as páginas de arquivo possíveis e trocar todas as páginas anônimas para a ZRAM, minimizando
o consumo de memória residente do app.
É possível conseguir a mesma "reivindicação máxima" manualmente lendo memory.current
e gravando de volta em memory.reclaim:
# Force reclaim of everything
adb shell "cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.current > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"
Exercício: forçar a recuperação e o rastreamento
Agora, vamos forçar o kernel a recuperar a memória do MemoryLab e capturar a atividade em um rastro do Perfetto.
- Preparação: verifique se o MemoryLab tem algumas alocações (Java e Bitmaps).
Iniciar rastreamento: use essa configuração de rastreamento inline para capturar eventos de programação, recuperação e cache de página.
# Use a config that captures scheduling, reclaim and page cache events adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/reclaim.perfetto-trace <<EOF buffers: { size_kb: 131072 fill_policy: RING_BUFFER } data_sources: { config { name: "linux.ftrace" ftrace_config { ftrace_events: "sched/sched_switch" ftrace_events: "sched/sched_wakeup" ftrace_events: "kmem/rss_stat" ftrace_events: "mm_filemap_add_to_page_cache" ftrace_events: "mm_filemap_delete_from_page_cache" ftrace_events: "vmscan/mm_vmscan_direct_reclaim_begin" ftrace_events: "vmscan/mm_vmscan_direct_reclaim_end" ftrace_events: "vmscan/mm_vmscan_memcg_reclaim_begin" ftrace_events: "vmscan/mm_vmscan_memcg_reclaim_end" symbolize_ksyms: true } } } data_sources: { config { name: "linux.process_stats" process_stats_config { scan_all_processes_on_start: true } } } EOFAcionar pressão e recuperação:
- No MemoryLab, toque em Allocate Native Memory (1GB). Isso vai acionar uma pressão em todo o sistema.
Em um terminal separado, recupere 200 MB:
adb shell "echo 200M > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"
Fault Back In: volte para o MemoryLab. Toque em Thrash Pagecache (Refault test).
Interromper o trace: pressione Ctrl+C no terminal de trace.
Como analisar a recuperação no Perfetto
Ao abrir o rastro, você pode ver a recuperação em todo o sistema e por app em ação:

Procure o seguinte no trace:
- kswapd: pesquise
kswapd0em Kernel threads (na captura de tela acima, ele foi fixado manualmente na parte de cima). Você vai notar que ele é ativado e executado (fatias verdes) enquanto o sistema tenta encontrar páginas livres. - Direct Reclaim: confira as linhas de execução do processo
com.android.memorylab. Você vai ver fatias de eventos ftrace roxas (comomm_vmscan_direct_reclaim_begin) aparecendo diretamente abaixo da faixa de programação da linha de execução. Isso indica que a própria linha de execução do app está travada, aguardando que o kernel libere páginas. - Contadores de RSS e Swap:
mem.rss.anon: aumenta conforme você toca nos botões de alocação.mem.swap: aumenta de forma constante à medida que okswapde as próprias linhas de execução do app (sujeitas à recuperação direta) compactam essas páginas anônimas na ZRAM.
- Reclamação de memcg: se você ampliar o momento em que acionou a reclamação manual, verá uma queda acentuada em
rss.anonerss.file, acompanhada por eventosmm_vmscan_memcg_reclaim.
← Em todo o sistema | ↑ Para cima | Interação entre kswapd e lmkd →