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 (mmapping) para gerenciar a memória (links em inglês). Isso significa que qualquer memória que um app modifique, seja alocando novos objetos ou tocando em páginas mapeadas em memória, 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 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 ingê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.