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 de apps individuais.
É fundamental entender o memcg 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 das pegadas de memória do app.
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. O memcg é o controlador de cgroup específico para memória.
Conceitos principais do memcg
- Cobrança do memcg: quando um processo em um memcg aloca uma página de memória (anônima ou com suporte a arquivos), essa página é "cobrada" do memcg. A cobrança 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 pode ter limites (como
memory.maxoumemory.high) que acionam a recuperação ou até mesmo o OOM killer se forem excedidos, independentemente da memória global do sistema. - Recuperação por memcg: quando um memcg está acima do limite ou quando o sistema precisa de memória, o kernel pode segmentar um memcg específico para recuperação. Isso significa despejar as páginas de arquivo ou trocar as páginas anônimas para ZRAM.
A hierarquia do 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 aplicativos Android.uid_<UID>/: um diretório para o ID de usuário de cada app. Todos os processos pertencentes 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 o controle refinado e a contabilização de 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 cobrança de memória em tempo real.
1. Iniciar o MemoryLab
Verifique se o MemoryLab está em execução no dispositivo.
2. Encontrar o memcg do MemoryLab
Primeiro, receba o PID do app MemoryLab em execução:
adb shell pidof com.android.memorylab
# Example output: 11672
Agora, localize o diretório cgroup. Ele pode ser encontrado 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 do memcg em relação a /sys/fs/cgroup. Portanto, o caminho completo é /sys/fs/cgroup/apps/uid_10274/pid_11672/.
3. Ler estatísticas do memcg
Acesse esse diretório e confira os arquivos principais:
# 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 suporte a arquivos (cache de página). *
swap: quantidade de memória trocada para 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. Ele deve aumentar em aproximadamente 10 MB para cada toque. - Toque em Allocate Bitmaps. Verifique
memory.statpara ver o aumento deanon.
Recuperação proativa com memory.reclaim
Um recurso avançado do memcg (v2) é o arquivo memory.reclaim. A gravação de um valor nesse arquivo instrui o kernel a tentar recuperar imediatamente essa quantidade de memória desse memcg e de todos os memcgs abaixo dele.
Como o Android usa memory.reclaim
O CachedAppOptimizer do Android usa esse recurso para recuperar ao máximo a 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 atual de memória do app no arquivo memory.reclaim. Isso força o kernel a despejar todas as páginas de arquivo possíveis e trocar todas as páginas anônimas para ZRAM, minimizando a pegada residente do app.
É possível alcançar a mesma "recuperação máxima" manualmente lendo memory.current e gravando-o 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 rastreamento do Perfetto.
- Preparar: verifique se o MemoryLab tem algumas alocações (Java e bitmaps).
Iniciar o rastreamento: use esta configuração de rastreamento inline para capturar eventos de agendamento, 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 a 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"
Falha de volta: volte para o MemoryLab. Toque em Thrash Pagecache (Refault test).
Parar o rastreamento: pressione Ctrl + C no terminal de rastreamento.
Como analisar a recuperação no Perfetto
Ao abrir o rastreamento, você pode conferir a recuperação em todo o sistema e por app em ação:

Procure o seguinte no rastreamento:
- kswapd: pesquise
kswapd0em Kernel threads (na captura de tela acima, ele foi fixado manualmente na parte de cima). Você vai notar que ele está ativando e executando (fatias verdes) à medida que o sistema tenta encontrar páginas livres. - Recuperação direta: confira as linhas de execução do processo
com.android.memorylab. Você vai notar fatias de eventos ftrace roxas (comomm_vmscan_direct_reclaim_begin) aparecendo diretamente na faixa de agendamento da linha de execução. Isso indica que a linha de execução do app está paralisada aguardando que o kernel libere páginas. - Contadores de RSS e troca:
mem.rss.anon: aumenta quando você toca nos botões de alocação.mem.swap: aumenta constantemente à medida quekswapde as próprias linhas de execução do app (sujeitas à recuperação direta) compactam essas páginas anônimas em ZRAM.
- Recuperação do memcg: se você aumentar o zoom no momento em que acionou a recuperação manual, vai notar uma queda acentuada em
rss.anonerss.file, acompanhada de eventosmm_vmscan_memcg_reclaim.
← Em todo o sistema | ↑ Acima | Interação kswapd e lmkd →