Visão geral do gerenciamento de memória

O Android Runtime (ART) e a máquina virtual Dalvik usam paginação e mapeamento em memória (em inglês) (mmapping) para gerenciar a memória. Isso significa que qualquer memória que um app modifique, seja alocando novos objetos ou tocando em páginas mapeadas, continua sendo armazenada na RAM e não pode ser despaginada. A única maneira de liberar a memória de um app é liberar as referências de objetos mantidas por ele, disponibilizando a memória para o coletor de lixo. Isso ocorre com uma exceção: todos os arquivos mapeados em memória sem modificação, como códigos, podem ser despaginados da RAM caso o sistema queira usar essa memória em outro lugar.

Esta página explica como o Android gerencia processos de apps e alocação de memória. Para saber mais sobre como gerenciar memória de forma mais eficiente no seu app, consulte Gerenciar a memória do seu app.

Coleta de lixo

Um ambiente de memória gerenciada, como o ART ou a máquina virtual Dalvik, monitora cada alocação de memória. Depois que ele determina que uma parte da memória não está mais sendo usada pelo programa, ela é liberada de volta para o heap, sem qualquer intervenção do programador. O mecanismo para liberar memória não utilizada em um ambiente de memória gerenciada é conhecido como coleta de lixo. A coleta de lixo tem dois objetivos: localizar objetos de dados em um programa que não pode ser acessado no futuro e liberar os recursos usados por esses objetos.

O heap de memória do Android é geracional, o que significa que há diferentes buckets de alocações rastreados com base na vida útil esperada e no tamanho de um objeto alocado. Por exemplo, objetos alocados recentemente pertencem à geração jovem. Quando um objeto permanece ativo por tempo suficiente, ele pode ser promovido a uma geração mais antiga, seguida por uma geração permanente.

Cada geração de heap tem o próprio limite máximo dedicado com relação ao volume de memória que os objetos podem ocupar. Sempre que uma geração começa a ser preenchida, o sistema executa um evento de coleta de lixo para tentar liberar memória. A duração da coleta de lixo depende da geração de objetos que ela está coletando e da quantidade de objetos ativos em cada geração.

Mesmo que a coleta de lixo possa ser bastante rápida, ela ainda pode afetar a performance do app. Em geral, não é possível controlar, com seu código, quando um evento de coleta de lixo ocorre. O sistema tem um conjunto de critérios para determinar quando a coleta de lixo deve ser feita. Quando os critérios são atendidos, o sistema interrompe a execução do processo e inicia a coleta de lixo. Se a coleta de lixo ocorrer no meio de uma repetição de processamento intensivo, como uma animação ou durante a reprodução de uma música, o tempo de processamento pode aumentar. Esse aumento pode fazer a execução de código no app ultrapassar o limite recomendado de 16 ms para a renderização eficiente e uniforme de frames.

Além disso, o fluxo de código pode realizar tipos de trabalho que forçam os eventos de coleta de lixo a ocorrer com mais frequência ou que os fazem durar mais tempo que o normal. Por exemplo, se você alocar vários objetos na parte mais interna de uma repetição "for" durante cada frame de uma animação de mistura Alfa, vai poluir o heap da memória com muitos objetos. Nessa circunstância, o coletor de lixo executa vários eventos de coleta de lixo e pode prejudicar a performance do app.

Para ver informações mais gerais sobre a coleta de lixo, consulte Coleta de lixo (link em inglês).

Compartilhar memória

Para ajustar tudo o que é necessário na RAM, o Android tenta compartilhar páginas da RAM entre os processos. Isso pode ser feito destas maneiras:

  • Cada processo do app é bifurcado de um processo existente denominado "Zigoto". O processo Zigoto começa quando o sistema inicializa e carrega códigos e recursos comuns do framework, como temas da atividade. Para iniciar um novo processo do app, o sistema bifurca o processo Zigoto e, em seguida, carrega e executa o código do app no novo processo. Essa abordagem permite que a maioria das páginas da RAM alocadas para o código e os recursos do framework seja compartilhada em todos os processos do app.
  • A maioria dos dados estáticos é mapeada em memória para um processo. Essa técnica permite que os dados sejam compartilhados entre processos e paginados quando necessário. Exemplos de dados estáticos incluem: código Dalvik (colocado em um arquivo .odex vinculado com antecedência para mapeamento direto), recursos do app (projetando a tabela de recursos para ser uma estrutura que pode ser mapeada e alinhando as entradas zip do APK) e elementos tradicionais do projeto, como código nativo em arquivos .so.
  • Em muitos lugares, o Android compartilha a mesma RAM dinâmica entre processos usando regiões de memória compartilhada explicitamente alocadas (com ashmem ou gralloc). Por exemplo, as superfícies de janela usam memória compartilhada entre o app e o compositor de tela, e os buffers do cursor usam a memória compartilhada entre o provedor de conteúdo e o cliente.

Devido ao uso extensivo de memória compartilhada, é preciso ter cuidado para determinar a quantidade de memória que seu app está usando. As técnicas para determinar corretamente o uso de memória no seu app são discutidas em Investigar o uso de RAM.

Alocar e liberar memória do app

O heap Dalvik é restrito a um único intervalo de memória virtual para cada processo do app. Isso define o tamanho lógico do heap, que pode aumentar conforme o necessário, mas apenas até um limite definido pelo sistema para cada app.

O tamanho lógico do heap não é o mesmo que a quantidade de memória física usada por ele. Ao inspecionar o heap do app, o Android calcula um valor conhecido como tamanho do conjunto proporcional (PSS, na sigla em inglês), que considera páginas limpas e sujas que são compartilhadas com outros processos, mas apenas em uma quantidade proporcional ao número de apps compartilhados pela RAM. Esse total de PSS é o que o sistema considera como a memória física. Para saber mais sobre o PSS, consulte o guia Inspecionar o uso de RAM.

O heap Dalvik não compacta o tamanho lógico do heap, o que significa que o Android não desfragmenta o heap para fechar espaço. O Android só pode reduzir o tamanho lógico do heap quando há espaço não utilizado no final do heap. No entanto, o sistema ainda pode reduzir a memória física usada pelo heap. Depois da coleta de lixo, a máquina virtual Dalvik percorre o heap e encontra páginas não utilizadas. Em seguida, ela retorna essas páginas ao kernel usando o madvise. As alocações pareadas e as desalocações de blocos grandes devem resultar na liberação de toda (ou quase toda) a memória física usada. No entanto, a liberação de memória de pequenas alocações pode ser muito menos eficiente, porque a página usada para uma delas ainda pode ser compartilhada com algo que ainda não foi liberado.

Restringir a memória do app

Para manter um ambiente multitarefa funcional, o Android define um limite rígido para o tamanho do heap de cada app. O limite exato de tamanho do heap varia entre dispositivos com base na quantidade de RAM disponível para cada aparelho. Se o app atingir a capacidade de heap e tentar alocar mais memória, ele pode encontrar um OutOfMemoryError.

Em alguns casos, você pode consultar o sistema para determinar exatamente quanto espaço de heap está disponível no dispositivo atual. Por exemplo, para determinar quantos dados devem ser mantidos em cache. É possível consultar o sistema para determinar esse número chamando getMemoryClass(). Esse método retorna um número inteiro, indicando o número de megabytes disponíveis para o heap do app.

Alternar entre apps

Quando os usuários alternam entre apps, o Android mantém em um cache os apps que não estão em primeiro plano, ou seja, não estão visíveis para o usuário ou estão executando serviços de primeiro plano, como reprodução de músicas. Por exemplo, quando o usuário abre um app pela primeira vez, um processo é criado para ele. Mas, quando o usuário sai do app, esse processo não é encerrado. O sistema mantém o processo em cache. Se o usuário retornar ao app mais tarde, o sistema vai reutilizar o processo, tornando a alternância de apps mais rápida.

Se o app tiver um processo armazenado em cache e reter recursos que não são necessários no momento, mesmo que o usuário não os use, a performance geral do sistema vai ser afetada. Conforme o sistema fica sem recursos, como memória, ele encerra processos no cache. O sistema também considera quais processos estão usando a maior parte da memória e pode os encerrar para liberar a RAM.

Observação: quanto menos memória um app consumir enquanto estiver no cache, melhores são as chances dele não ser encerrado e poder ser retomado rapidamente. No entanto, dependendo dos requisitos instantâneos do sistema, é possível encerrar processos em cache a qualquer momento, independente da utilização de recursos.

Para saber mais sobre como os processos são armazenados em cache enquanto não são executados no primeiro plano e como o Android decide quais deles podem ser encerrados, consulte o guia Processos e linhas de execução.

Teste de estresse de memória

Embora os problemas de estresse de memória sejam menos comuns em dispositivos de última geração, eles ainda podem causar problemas para usuários em dispositivos com pouca memória RAM, como aqueles que usam o Android (versão Go). É importante tentar reproduzir esse ambiente de estresse de memória para programar testes de instrumentação para verificar o comportamento do app e melhorar a experiência dos usuários em dispositivos com pouca memória.

Teste de aplicativo estressante

O Teste de aplicativo estressante (stressapptest) é um teste de interface de memória que ajuda a criar situações realistas de alta carga para testar várias limitações de memória e hardware do app. Com a capacidade de definir limitações de tempo e memória, é possível programar a instrumentação para verificar situações reais com grande quantidade de memória. Por exemplo, use o seguinte conjunto de comandos para enviar a biblioteca estática ao sistema de arquivos de dados, torná-la executável e executar um teste de estresse por 20 segundos com 990 MB:
    adb push stressapptest /data/local/tmp/
    adb shell chmod 777 /data/local/tmp/stressapptest
    adb shell /data/local/tmp/stressapptest -s 20 -M 990

  

Consulte a documentação stressapptest para ver mais informações sobre como instalar a ferramenta, argumentos comuns e informações sobre como lidar com erros.

Observações sobre o stressapptest

Ferramentas como stressapptest podem ser usadas para solicitar alocações de memória maiores que as disponíveis livremente. Esse tipo de solicitação pode gerar vários alertas, que você precisa conhecer para fins de desenvolvimento. Veja três alertas principais que podem ser gerados por causa da baixa disponibilidade de memória:
  • SIGABRT: uma falha nativa fatal para seu processo devido à solicitação de alocações de tamanho maior que a memória livre, enquanto o sistema já está sob pressão da memória.
  • SIGQUIT: produz um despejo de memória principal e encerra o processo quando detectado pelo teste de instrumentação.
  • TRIM_MEMORY_EVENTS: callbacks que estão disponíveis no Android 4.1 (API de nível 16) e versões mais recentes e fornecem alertas de memória detalhados para seu processo.