Para otimizar de maneira eficaz o consumo de memória do jogo, primeiro é necessário entender como a plataforma Android mede a memória e como usar a telemetria do sistema, as APIs de diagnóstico e as ferramentas de criação de perfil. Este guia detalha como monitorar, capturar e analisar as alocações de memória do jogo de acordo com as novas diretrizes da plataforma.
Entender as métricas de RSS e de troca
Para analisar e depurar de maneira eficaz o comportamento da memória do jogo, é necessário entender as métricas técnicas exatas que a plataforma Android usa para a aplicação da memória. Para informações detalhadas sobre como esse parâmetro de telemetria é processado e monitorado, consulte a documentação do Android Vitals: uso da memória (RSS anônimo + troca).
1. RSS anônimo (RssAnon)
O tamanho do conjunto residente (RSS, na sigla em inglês) mede a parte da memória ocupada por um processo que é mantida na RAM física do dispositivo. O RSS é dividido em memória com suporte de arquivo e memória anônima. A métrica de aplicação do Android se concentra estritamente no RSS anônimo:
- O que inclui: páginas de memória alocadas diretamente pelo processo do jogo que não estão vinculadas a um arquivo físico no armazenamento. Essas páginas incluem heaps Java ou Kotlin, pilhas de execução de linhas de execução e, principalmente, alocações de memória nativa (como alocadores de mecanismo C++ personalizados ou blocos de memória solicitados usando malloc nativo ou novo e sujo pela lógica do jogo). Saiba mais sobre essa métrica no dicionário de memória de processo (RSS).
- Por que é importante: os mecanismos de jogos usam pools de memória nativa enormes para lidar com física, renderização e lógica. Como esses pools não são apoiados por arquivos, eles ficam totalmente no RSS anônimo e formam a maior parte do espaço físico do jogo.
2. Troca não compactada (VmSwap)
O Android não oferece suporte a um espaço de troca tradicional baseado em disco devido ao desgaste do armazenamento flash e às restrições de latência. Em vez disso, ele usa a zRAM (troca não compactada):
- O que inclui: quando a pressão da RAM física aumenta, o daemon de gerenciamento de memória do kernel compacta páginas anônimas inativas e as move para uma parte dedicada e não compactada da RAM física (zRAM).
- O cálculo da métrica: o sistema rastreia isso com base no tamanho não compactado (VmSwap) para avaliar a demanda real de memória física do jogo. Se o jogo alocar memória e o sistema a trocar para a zRAM, ela ainda será contabilizada no espaço total de memória do jogo.
3. Estados do processo
O uso da memória é dividido por estados de processo no Android vitals. Para desenvolvedores de jogos, SDKs ou jogos de terceiros também podem acionar serviços percebidos pelo usuário ou serviços em segundo plano de maneira inesperada.
- O que inclui: primeiro plano, serviços perceptíveis, segundo plano e em cache.
- Por que é importante: diferentes estados de processo têm impactos diferentes no
gerenciamento de memória do SO Android. Talvez você não saiba que o jogo está sendo executado com um estado de processo sensível se algum dos SDKs de terceiros acionar uma tarefa em segundo plano sem querer. Monitore se o jogo está sendo executado em segundo plano
usando
RunningAppProcessInfo.
Interfaces de programação de aplicativos (APIs)
O Android oferece APIs do sistema que permitem que o jogo responda dinamicamente à pressão da memória e capture diagnósticos detalhados de memória durante a execução.
Responder a eventos de redução de memória
O sistema usa onTrimMemory para notificar o app sobre eventos de ciclo de vida que
apresentam uma boa oportunidade para que ele reduza voluntariamente o uso da memória
e evite ser encerrado pelo Low Memory Killer (LMK) para liberar memória para
outros apps.
Se o sistema encerrar o app em segundo plano, o usuário vai ter uma inicialização a frio lenta ao retomar. Reduzir o uso da memória em segundo plano ajuda a evitar esses encerramentos.
Ao responder a eventos de redução, libere alocações de memória grandes e reconstruíveis que não são necessárias imediatamente:
Exemplo: reduza ou limpe bitmaps em cache (decodificados do armazenamento local) em resposta a
TRIM_MEMORY_UI_HIDDEN.
Kotlin
class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
override fun onTrimMemory(level: Int) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
Java
public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
public void onTrimMemory(int level) {
switch (level) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
}
ProfilingManager
Introduzida no Android 15 (nível 35 da API), a ProfilingManager API permite que
aplicativos capturem snapshots definidos programaticamente (como perfis de heap
, rastreamentos do sistema e dumps de heap Java) diretamente durante a execução.
Os desenvolvedores podem acionar capturas manualmente em cenas específicas ou registrar acionadores automatizados, como TRIGGER_TYPE_ANOMALY, para acionar automaticamente uma captura quando o processo do jogo viola os limites do Memory Limiter. No entanto, os desenvolvedores de jogos precisam considerar limitações críticas em mecanismos de jogos modernos:
Observação:mecanismos de jogos modernos (como Unity ou Unreal) gerenciam o desempenho da execução pré-alocando blocos de memória virtual enormes do kernel usando mmap com a flag MAP_ANONYMOUS. Os mecanismos usam subalocadores personalizados (por exemplo, o gerenciador de memória nativa do Unity ou os BinnedAllocators do Unreal) para subdividir e alocar blocos de memória internamente.
ApplicationExitInfo
Se o jogo for encerrado em segundo plano ou encerrado por violar os limites de memória de processos individuais, os mecanismos padrão de crash dump nativas ou Java (como o Firebase Crashlytics) não vão registrar o evento. Para consultar e registrar esses
encerramentos de maneira programática, os desenvolvedores precisam usar a
ApplicationExitInfo API na inicialização do jogo.
- Implementação: na inicialização, chame
ActivityManager.getHistoricalProcessExitReasons()para recuperar os motivos de saída das sessões recentes. - Principais motivos de saída de memória:
REASON_LOW_MEMORY: indica que o processo foi encerrado pelo Low Memory Killer (LMK) do sistema. Esse encerramento ocorre quando a pressão de memória em todo o dispositivo é alta e o SO precisa recuperar a RAM. Esse motivo de saída indica que o espaço em segundo plano do jogo é muito grande para coexistir com outros aplicativos.REASON_MEMORY_LIMITER(Android 17 (nível 37 da API) e mais recentes): indica que o processo foi encerrado especificamente porque excedeu o limite de memória do cgroup (RssAnon + VmSwap) atribuído pelo Memory Limiter da plataforma. Esse encerramento pode acontecer mesmo que haja bastante memória física restante no dispositivo, o que significa uma violação direta dos limites de processos individuais.
Use as ferramentas disponíveis
Use as seguintes ferramentas da plataforma durante o desenvolvimento e o controle de qualidade para medir com precisão o uso da memória do jogo.
meminfo
Essa ferramenta coleta estatísticas sobre a memória para mostrar quanta memória PSS foi alocada e as categorias para as quais ela foi usada.
Imprima as meminfo estatísticas de uma das seguintes maneiras:
- Use o comando
adb shell dumpsys meminfo package-name. - Use a chamada
MemoryInfoda API Android Debug.
A estatística PrivateDirty mostra a quantidade de RAM dentro do processo
que não pode ser paginada no disco e não é compartilhada com outros processos. A maior parte desse valor fica disponível para o sistema quando esse processo é encerrado.
Tracepoints de memória
Os tracepoints de memória rastreiam a quantidade de memória RSS que seu jogo está usando. Calcular o uso da memória RSS é muito mais rápido do que calcular o uso do PSS. Como é mais rápido de calcular, o RSS mostra uma granularidade mais refinada nas alterações no tamanho da memória para medições mais precisas do uso da memória de pico. Portanto, é mais fácil observar picos que possam fazer com que o jogo fique sem memória.
Perfetto
O Perfetto (link em inglês) é um pacote de ferramentas para coletar informações de desempenho e memória
de um dispositivo e exibi-lo em uma interface baseada na Web. Ele oferece suporte a rastreamentos arbitrariamente longos para que você possa visualizar como o RSS muda ao longo do tempo. Também é possível emitir consultas SQL dos dados produzidos para processamento off-line. Ative os rastreamentos longos
no app Rastreamento do sistema. Verifique se a categoria
está ativada para o rastreamento.memory:Memory Para instrumentação de memória personalizada no desenvolvimento e
teste, também é possível usar a API heapprofd (Beta).
Inspecionar o RssAnon e a troca no Perfetto
Para verificar o impacto da memória anônima e da troca zRAM do jogo, carregue o arquivo de rastreamento na interface baseada na Web em ui.perfetto.dev e siga estas técnicas analíticas, projetadas para estudos de caso de memória detalhados (consulte Estudos de caso de análise de memória do Perfetto para mais detalhes):
1. Visualizar contadores de memória na linha do tempo
- Localize o processo: na lista de navegação, pesquise o nome do pacote ou do processo do jogo.
- Expandir grupo de rastreamento: clique na linha do processo para expandir as faixas de linhas de execução e localize o subgrupo chamado "Memória".
- Analisar as faixas:
- mem.rss.anon (RSS anônimo): esse gráfico de linhas mostra a RAM física em tempo real ocupada pelos pools de memória não gerenciados do jogo. Monitore essa linha do tempo durante os carregamentos de cena, pop-ups da interface ou transições de jogabilidade para verificar picos de alocação altos.
- mem.swap (troca compactada ou VmSwap): esse gráfico mostra o tamanho pré-compactado dos blocos de memória movidos para a zRAM. A alta atividade de troca que coincide com a jogabilidade indica que o jogo está sendo executado em um dispositivo com memória restrita e que o sistema está compactando ativamente os recursos em segundo plano.
2. Executar consultas SQL (processador de rastreamento) Para uma análise off-line detalhada, você pode executar consultas SQL diretamente no console da interface do Perfetto ou usar a biblioteca Python do processador de rastreamento independente para calcular picos estatísticos.
Encontre a alocação de RSS anônima de pico:
SELECT max(value) / 1024 / 1024 AS max_rss_anon_mb FROM counter JOIN counter_track ON counter.track_id = counter_track.id WHERE counter_track.name = 'mem.rss.anon' AND counter_track.upid IN ( SELECT upid FROM process WHERE name = 'your.game.package.name' );Correlacione RssAnon e VmSwap em qualquer carimbo de data/hora:
SELECT ts, track.name AS metric_type, value / 1024 / 1024 AS size_mb FROM counter JOIN counter_track track ON counter.track_id = track.id WHERE (track.name = 'mem.rss.anon' OR track.name = 'mem.swap') AND track.upid IN ( SELECT upid FROM process WHERE name = 'your.game.package.name' ) ORDER BY ts ASC;
Para mais detalhes sobre como inspecionar arquivos de rastreamento usando o Android Studio, consulte Inspecionar rastreamentos do sistema: memória de processo (RSS). Para detalhes sobre como criar scripts de perfis de memória, consulte Gravar alocações nativas.
Heapprofd
heapprofd é uma ferramenta de rastreamento de memória que faz parte do Perfetto. Essa ferramenta pode ajudar a encontrar vazamentos de memória ao mostrar onde a memória foi alocada usando malloc. O heapprofd pode ser iniciado usando um script Python. Como a ferramenta tem baixa sobrecarga, isso não afeta o desempenho como outras ferramentas, como a Malloc Debug.
Relatório de bugs
bugreport é uma ferramenta de geração de registros para descobrir se o jogo falhou porque ficou sem memória. A saída da ferramenta é muito mais detalhada do que a do logcat. Ele é útil para depuração de memória porque mostra se o jogo falhou por falta de memória ou se foi encerrado pelo LMK.
Para mais informações, consulte Capturar e ler relatórios de bugs.
Ferramentas do mecanismo de jogo
Embora os registros no nível da plataforma e a telemetria do sistema sejam essenciais para rastrear limites e conformidade do SO, as ferramentas específicas do mecanismo de jogo ajudam a atribuir alocações diretamente aos objetos do jogo, comportamentos de script e hierarquias de cena ativas.
Unity
Em um ambiente do Unity Engine, é possível estimar de perto o consumo de memória do RSS anônimo + troca do Android durante o tempo de execução com alta confiabilidade (normalmente mostrando uma variância de menos de 10% em comparação com os valores reais no nível do SO) usando as ferramentas e classes de criação de perfil nativas do Unity.
Para um tutorial completo, incluindo regras de configuração e scripts de execução, consulte Como verificar a memória com as ferramentas do Unity.
- API do criador de perfil do Unity: é possível aproximar programaticamente o consumo de memória não gerenciado do jogo durante o tempo de execução consultando as métricas principais do mecanismo:
- Usando a classe do criador de perfil: rastreie as alocações de memória totais somando
os valores de
Profiler.GetTotalReservedMemoryLong()eProfiler.GetMonoHeapSizeLong(). - Usando a classe
ProfilerRecorder: Monitore as categorias de memória dinamicamente. Para estabelecer uma aproximação de linha de base confiável, busque a memória reservada total (em builds de lançamento) ou subtraia a memória reservada de Gfx dela (em builds de desenvolvimento) para remover os componentes de memória gráfica com suporte de arquivo.
- Usando a classe do criador de perfil: rastreie as alocações de memória totais somando
os valores de
- Memory Profiler do Unity: para identificar e depurar vazamentos de memória off-line,
capture um snapshot de memória e inspecione o gráfico de memória residente no dispositivo
encontrado na seção "Toda a memória". Para calcular o espaço aproximado, some os totais das seguintes categorias: não rastreado, Android Runtime, nativo e gerenciado.
- Limitação da zRAM: em condições de memória restrita, o kernel do Android pode compactar páginas de memória inativas no espaço de troca (zRAM). Como o Memory Profiler do Unity não consegue detectar parâmetros de troca no nível do SO, você pode encontrar pequenas discrepâncias de espaço durante cenas de memória pesadas. Compare suas estimativas com o Perfetto para confirmar os valores exatos.