A máquina virtual do Android Runtime (ART) e Dalvik usa paging e memory-mapping (mmapping) para gerenciar a memória. Isto significa que qualquer memória que um aplicativo modifica – seja alocando novos objetos ou tocando em páginas mmapped – retorna à memória residente na RAM e não pode ser paginada. A única maneira de liberar memória de uma aplicação é liberar referências de objetos que a aplicação mantém, tornando a memória disponível para o coletor de lixo. Isso com uma exceção: qualquer arquivo mmapped in sem modificação, como código, pode ser paginado fora da RAM se o sistema quiser usar essa memória em outro lugar.
Esta página explica como o Android gerencia os processos de aplicação e alocação de memória. Para mais informações sobre como gerenciar a memória mais eficientemente em seu aplicativo, veja Manage Your App’s Memory.
Garbage collection
Um ambiente de memória gerenciada, como a máquina virtual ART ou Dalvik, mantém o controle de cada alocação de memória. Uma vez que determina que um pedaço de memória não está mais sendo usado pelo programa, ele o libera de volta para a pilha, sem qualquer intervenção do programador. O mecanismo de recuperação de memória não utilizada dentro de um ambiente de memória gerenciada é conhecido como coleta de lixo. A coleta de lixo tem dois objetivos: encontrar objetos de dados em um programa que não possam ser acessados no futuro; e recuperar os recursos usados por esses objetos.
A pilha de memória do Android é uma pilha geracional, o que significa que há diferentes baldes de alocações que ele rastreia, com base na vida esperada e no tamanho de um objeto sendo alocado. Por exemplo, os objetos alocados recentemente pertencem à geração Young. Quando um objeto permanece ativo o tempo suficiente, ele pode ser promovido para uma geração mais velha, seguido por uma geração permanente.
Cada geração de pilha tem seu próprio limite superior dedicado à quantidade de memória que os objetos ali podem ocupar. Sempre que uma geração começa a se encher, o sistema executa um evento de coleta de lixo na tentativa de liberar memória. A duração da coleta de lixo depende de qual geração de objetos está coletando e quantos objetos ativos estão em cada geração.
Mesmo que a coleta de lixo possa ser bastante rápida, ela ainda pode afetar o desempenho do aplicativo. Você geralmente não controla quando um evento de coleta de lixo ocorre de dentro do seu código. O sistema tem um conjunto de critérios em execução para determinar quando realizar a coleta de lixo. Quando os critérios são satisfeitos, o sistema pára de executar o processo e inicia a coleta de lixo. Se a coleta de lixo ocorrer no meio de um ciclo de processamento intensivo como uma animação ou durante a reprodução da música, pode aumentar o tempo de processamento. Esse aumento pode potencialmente empurrar a execução do código em seu aplicativo para além do limite recomendado de 16ms para uma renderização eficiente e suave de quadros.
Além disso, seu fluxo de código pode executar tipos de trabalho que forçam os eventos de coleta de lixo a ocorrerem com mais frequência ou fazê-los durar mais que o normal. Por exemplo, se você alocar múltiplos objetos na parte mais interna de um for-loop durante cada frame de uma animação alpha blending, você pode poluir o seu monte de memória com muitos objetos. Nessa circunstância, o lixeiro executa múltiplos eventos de coleta de lixo e pode degradar a performance do seu app.
Para informações mais gerais sobre coleta de lixo, veja Garbage collection.
A fim de caber tudo o que ele precisa na RAM, o Android tenta compartilhar páginas da RAM através de processos. Ele pode fazer isso das seguintes maneiras:
- Cada processo de aplicação é bifurcado a partir de um processo existente chamado Zygote. O processo Zygote começa quando o sistema inicia e carrega código estrutural e recursos comuns (tais como temas de atividade). Para iniciar um novo processo de aplicação, o sistema forca o processo Zygote e depois carrega e executa o código da aplicação no novo processo. Esta abordagem permite que a maioria das páginas RAM alocadas para o código estrutural e os recursos sejam compartilhados em todos os processos da aplicação.
- A maioria dos dados estáticos é mmapeada em um processo. Esta técnica permite que os dados sejam compartilhados entre processos, e também permite que eles sejam paginados quando necessário. Exemplos de dados estáticos incluem: Código Dalvik (colocando-o em um arquivo
.odex
para mmapping direto), recursos da aplicação (desenhando a tabela de recursos para ser uma estrutura que pode ser mmapped e alinhando as entradas zip do APK), e elementos tradicionais do projeto como código nativo em.so
arquivos. - Em muitos lugares, o Android compartilha a mesma RAM dinâmica através de processos usando regiões de memória compartilhada explicitamente alocadas (seja com ashmem ou gralloc). Por exemplo, superfícies de janela usam memória compartilhada entre o aplicativo e o compositor de tela, e buffers de cursor usam memória compartilhada entre o provedor de conteúdo e o cliente.
Devido ao uso extensivo de memória compartilhada, determinar quanta memória a sua aplicação está usando requer cuidado. Técnicas para determinar corretamente o uso de memória do seu aplicativo são discutidas em Investigando o uso de sua memória RAM.
Alocar e recuperar memória do aplicativo
A pilha Dalvik é restrita a uma única faixa de memória virtual para cada processo do aplicativo. Isto define o tamanho lógico da pilha, que pode crescer conforme necessário mas apenas até um limite que o sistema define para cada app.
O tamanho lógico da pilha não é o mesmo que a quantidade de memória física usada pela pilha. Ao inspecionar a pilha de aplicativos, o Android computa um valor chamado Proportional Set Size (PSS), que contabiliza páginas sujas e limpas que são compartilhadas com outros processos – mas apenas em uma quantidade que é proporcional a quantos aplicativos compartilham essa RAM. Este total (PSS) é o que o sistema considera ser a sua área de memória física. Para mais informações sobre PSS, consulte o guia Investigando seu uso da RAM.
A pilha Dalvik não compacta o tamanho lógico da pilha, o que significa que o Android não desfragmenta a pilha para fechar o espaço. O Android só pode diminuir o tamanho lógico da pilha quando há espaço não utilizado no final da pilha. No entanto, o sistema ainda pode reduzir a memória física utilizada pela pilha. Após a coleta de lixo, Dalvik anda na pilha e encontra páginas não utilizadas, depois devolve essas páginas para o kernel usando o madvise. Assim, alocações emparelhadas e desalocações de grandes pedaços devem resultar na recuperação de toda (ou quase toda) a memória física usada. No entanto, recuperar memória de pequenas alocações pode ser muito menos eficiente porque a página usada para uma pequena alocação ainda pode ser compartilhada com algo que ainda não foi liberado.
Restringir memória da aplicação
Para manter um ambiente multi-tarefa funcional, o Android estabelece um limite rígido no tamanho do heap para cada aplicação. O limite exato do tamanho do heap varia entre dispositivos com base na quantidade de RAM que o dispositivo tem disponível em geral. Se o seu aplicativo atingiu a capacidade da pilha e tenta alocar mais memória, ele pode receber um OutOfMemoryError
.
Em alguns casos, você pode querer consultar o sistema para determinar exatamente quanto espaço de pilha você tem disponível no dispositivo atual – por exemplo, para determinar quantos dados são seguros para manter em um cache. Você pode consultar o sistema para esta figura chamando getMemoryClass()
. Este método retorna um número inteiro indicando o número de megabytes disponíveis para o heap.
Switch apps
Quando os usuários alternam entre aplicativos, o Android mantém aplicativos que não estão em primeiro plano – isto é, não são visíveis para o usuário ou executando um serviço em primeiro plano, como playback- em um cache. Por exemplo, quando um usuário lança um aplicativo pela primeira vez, um processo é criado para ele; mas quando o usuário deixa o aplicativo, esse processo não desiste. O sistema mantém o processo em cache. Se o usuário retornar mais tarde ao aplicativo, o sistema reutiliza o processo, tornando a comutação do aplicativo mais rápida.
Se o aplicativo tiver um processo em cache e retiver recursos de que não precisa atualmente, então o aplicativo – mesmo enquanto o usuário não estiver usando-o – afeta o desempenho geral do sistema. Como o sistema funciona com poucos recursos como memória, ele mata processos no cache. O sistema também conta com os processos que se mantêm na maior quantidade de memória e pode terminá-los para liberar a RAM.
Nota: Quanto menos memória sua aplicação consumir enquanto estiver na cache, melhores são as chances de que ela não morra e seja capaz de retomar rapidamente. No entanto, dependendo dos requisitos instantâneos do sistema, é possível que os processos em cache sejam terminados a qualquer momento, não importa a utilização dos recursos.
Para mais informações sobre como os processos são mantidos em cache enquanto não são executados em primeiro plano e como o Android decide quais podem ser mortos, veja o guia Processos e Threads.