Sobre o gerenciamento de memória

A otimização da memória é fundamental para oferecer experiências de jogos estáveis e de alta performance no Android. Este guia oferece uma visão geral de por que a eficiência da memória é importante, como o sistema operacional Android gerencia e aplica limites de memória de processos e os novos limites expostos no Google Play Console para ajudar você a monitorar e melhorar a qualidade técnica do seu jogo.

A importância da otimização da memória

Otimizar a memória do jogo é essencial para manter a retenção de jogadores, expandir a compatibilidade de dispositivos e obedecer aos padrões de qualidade da plataforma:

  • Prevenção de inicialização a frio (experiência do usuário e retenção): quando um jogador sai temporariamente do jogo (por exemplo, para responder a uma notificação ou verificar uma mensagem), o sistema operacional coloca o processo do jogo em segundo plano. Se o consumo de memória em segundo plano do jogo for muito alto, o Low Memory Killer (LMK) do sistema vai priorizar o encerramento do processo do jogo para recuperar a RAM para tarefas em primeiro plano. Na próxima vez que o usuário retomar, em vez de uma retomada instantânea e perfeita, o jogo vai passar por uma inicialização a frio longa, recarregando completamente recursos gráficos pesados, áudio e binários do mecanismo de jogo do armazenamento. Manter o uso da memória em segundo plano baixo evita esses encerramentos silenciosos em segundo plano, preservando o estado do usuário e garantindo que os jogadores possam retomar a sessão imediatamente. Para mais detalhes sobre o comportamento do LMK do sistema, consulte o guia Android Vitals: Low memory killers.
  • Estabilidade do ecossistema e do dispositivo: o uso ineficiente da memória e os vazamentos de memória degradam a saúde geral do sistema. Quando a memória do sistema é escassa, ele enfrenta uma pressão severa, resultando em quedas na taxa de frames, instabilidade da interface e falhas de áudio. Se a pressão da memória for muito severa, o Low Memory Killer (LMK) do sistema vai encerrar agressivamente os processos em segundo plano, forçando outros aplicativos a passar por inicializações a frio lentas e perder o estado do usuário quando os jogadores alternam entre tarefas.
  • Encerramentos no nível da plataforma: a partir do Android 17 (nível 37 da API), o sistema é mais proativo no encerramento de processos que usam muita memória. Se o consumo do jogo for muito alto, o SO poderá encerrar o processo abruptamente sem gerar um stack trace padrão.
  • Compatibilidade de dispositivos: embora os dispositivos principais tenham de 12 GB a 16 GB de RAM, uma grande parte do público global de jogos usa dispositivos com 4 GB ou 6 GB de RAM. O gerenciamento adequado da memória garante que o jogo permaneça acessível e responsivo em todas as camadas de hardware sem exigir pacotes de recursos complexos e separados.

Entender a memória no Android

Para criar estratégias eficazes de orçamento de memória, os desenvolvedores precisam entender como a plataforma Android gerencia a memória física e como ela mede o consumo ativo do jogo.

Principais conceitos de memória do Android

Para conceitos básicos sobre o gerenciamento de memória no nível da plataforma, consulte a documentação oficial Visão geral do gerenciamento de memória. Esse recurso abrange quatro áreas arquitetônicas:

  • Visão geral da memória: o Android usa paginação e mapeamento de memória (mmap) para gerenciar a RAM. Ele não oferece suporte a um arquivo de troca tradicional no disco. Em vez disso, ele depende da compactação de páginas (usando zRAM) e da recuperação de páginas para liberar memória física.
  • Alocação de memória entre processos: o Android compartilha a RAM em todo o sistema. Ele atribui heaps específicos para a execução da máquina virtual Dalvik ou ART, permitindo que ambientes de desenvolvimento nativo (como mecanismos de jogos C++) solicitem memória do heap do sistema nativo.
  • Gerenciamento de memória de apps: operando em um modelo multiprocesso, o Android espera que os aplicativos monitorem o estado do ciclo de vida de forma dinâmica e liberem voluntariamente recursos desnecessários (como gráficos e bitmaps não armazenados em cache) para oferecer suporte à saúde do sistema.
  • Visão geral de processos e linhas de execução: o sistema categoriza os processos em uma hierarquia com base na visibilidade e importância percebidas pelo usuário, determinando quais processos são mantidos ativos e quais são encerrados primeiro em condições de pouca memória.

A métrica de consumo total de memória

O Memory Limiter do Android 17 no nível da plataforma avalia o consumo do processo usando o consumo total de memória em vez do tamanho residente total (RSS) ou do tamanho da memória virtual.

Consumo total de memória = RSS anônimo (RssAnon) + troca não compactada (VmSwap)

Para evitar que os jogos excedam os limites aplicados pela plataforma, os desenvolvedores precisam entender exatamente o que essas métricas representam no nível do sistema. Para mais informações sobre essas métricas, alocações de RAM física e como as páginas apoiadas por arquivos são processadas, consulte Entender as métricas de RSS e troca no guia Monitorar o uso da memória.

Restrições de memória

Para manter a estabilidade do sistema e garantir que os aplicativos não consumam recursos excessivos, a plataforma Android aplica limites de memória aos processos em execução.

Memory Limiter no Android 17 e versões mais recentes

O Android 17 e versões mais recentes aplicam limites de memória rigorosos por app usando o cgroup v2 do Linux para evitar que apps individuais causem instabilidade em todo o sistema. Para mais detalhes sobre a implementação técnica, consulte o guia Memory Limiter do AOSP e o artigo Priorizar a eficiência de memória: etapas essenciais para o Android 17 no blog.

  • Mecanismo: o Memory Limiter monitora todos os processos de aplicativos e atribui limites dinamicamente com base no estado do ciclo de vida do processo:
    • Processos visíveis (em primeiro plano): os processos de apps que mostram uma interface precisam executar um conjunto de trabalho de recursos maior e recebem um limite mais generoso.
    • Processos não visíveis (em segundo plano ou serviços): os processos de apps que fazem trabalhos ativos sem mostrar uma interface são restritos a um orçamento mais apertado e restritivo.
  • Atributos do kernel: o serviço depende de dois atributos principais:
    • memory.high: um limite flexível. Quando excedido, o kernel limita o processo e tenta recuperar a memória de forma agressiva. Essa recuperação pode causar degradação de performance no jogo.
    • memory.swap.max: aplica um limite máximo ao espaço de troca ou zRAM que o processo pode usar.
  • Comportamento de encerramento: se um processo continuar alocando memória anônima após memory.high e esgotar a capacidade de troca, as alocações vão falhar, e o SO vai encerrar o processo silenciosamente. Esse encerramento é registrado usando ApplicationExitInfo no motivo de saída do Memory Limiter (disponível a partir do Android 17, 26Q4).

Monitorar o uso da memória

Para otimizar a memória do jogo de forma eficaz, primeiro é necessário entender como a plataforma Android mede o consumo. O Android 17 atualiza a métrica de aplicação de memória para rastrear a soma do RSS anônimo (RssAnon) e da troca não compactada (VmSwap), excluindo a memória apoiada por arquivos ou privada da GPU. Este guia detalha como aproveitar ferramentas no nível do sistema, como o Perfetto e o meminfo, implementar APIs de diagnóstico, como ProfilingManager e onTrimMemory, e extrair alocações de memória precisas no Unity e no Unreal Engine. Entenda como criar um perfil preciso do jogo e evitar as instabilidades de performance associadas à pesquisa tradicional de memória de execução.

Para mais informações, consulte Monitorar o uso da memória.

Estratégias de redução de memória

Embora os mecanismos de jogos simplifiquem o desenvolvimento multiplataforma, o processamento de memória padrão deles pode acionar limites de memória no nível do SO. Esta página detalha etapas práticas de otimização especificamente adaptadas para Unity e Unreal Engine. Entenda por que depender do onTrimMemory baseado em Java pode causar deadlocks no Unity e como usar callbacks de ciclo de vida nativos. Você também vai descobrir otimizações importantes no nível do recurso, como usar a compactação de textura ASTC 8x8 e configurar o descarregamento de recursos, para manter o jogo funcionando sem problemas em todas as camadas de hardware.