Tudo o que você precisa saber sobre vazamentos de memória no Android.
Um dos principais benefícios do Java, ou, para ser mais preciso, da JVM (Máquina Virtual Java), é o coletor de lixo (GC). Podemos criar novos objectos sem nos preocuparmos em libertá-los da memória. O lixeiro se encarregará de alocar e liberar a memória para nós.
Não exatamente! Podemos evitar que o coletor de lixo liberte a memória para nós se não entendermos totalmente como o GC funciona.
Escrever um código sem um bom entendimento de como o GC funciona poderia fazer vazamentos de memória no aplicativo. Esses vazamentos podem afetar nossa aplicação desperdiçando memória não liberada e, eventualmente, fazer com que haja exceções e lapsos de memória.
- O que é vazamento de memória?
- Espere um minuto!!🥴
- Heap & Stack
- Mais sobre o Heap Memory
- Pergunte-se qual é o tamanho do heap para o seu aplicativo?
- Como pode verificar o tamanho da pilha do aplicativo para o seu dispositivo?
- Como funciona no mundo real?
- O que acontece quando os métodos são terminados?
- Conclusão
- E quanto ao Heap?
- Como funciona o coletor de lixo?
- O que acontece quando o coletor de lixo é executado?
- Quando e como acontecem vazamentos de memória?
- Como podemos causar um vazamento?
- Como podemos causar um vazamento usando threads?
- Fluxo regular
O que é vazamento de memória?
Failure to release unused objects from the memory
Failure to release unused objects from the memory significa que há objetos não utilizados na aplicação que o GC não pode limpar da memória.
Quando o GC não pode limpar da memória os objetos não utilizados, nós estamos com problemas. A unidade de memória que mantém os objetos não utilizados será ocupada até o fim da aplicação ou (até o fim do método).
até o fim do método? Sim, é isso mesmo. Temos dois tipos de vazamentos, vazamentos que ocupam a unidade de memória até o final da aplicação e vazamentos que ocupam a unidade de memória até o final do método. O primeiro é claro. O segundo precisa de mais esclarecimentos. Vamos tomar um exemplo para explicar isto! Assumamos que temos o método X. O método X está a fazer uma longa tarefa em segundo plano, e vai demorar um minuto a terminar. Além disso, o método X está segurando objetos não utilizados enquanto faz isso. Nesse caso, a unidade de memória será ocupada, e os objetos não utilizados não poderão ser limpos por um minuto até o final da tarefa. Após o término do método, o GC pode limpar os objetos não utilizados e recuperar a memória.
É isso que eu quero que você saiba por enquanto que voltaremos a isso mais tarde com algum código e visualização. SERÁ DIVERTIDO. 👹😜
Espere um minuto!!🥴
Antes de saltar para o contexto, vamos começar com o básico.
>
RAM, ou memória de acesso aleatório, é a memória em dispositivos andróides ou computadores que costumavam armazenar aplicações em execução corrente e seus dados.
>
I’m going to explain two main characters in the RAM, the first is the Heap, and the second is the Stack. Vamos passar para a parte divertida 🤩🍻.
Heap & Stack
>
Não vou demorar muito. Vamos direto ao ponto, uma breve descrição, o Stack é usado para alocação de memória estática enquanto o Heap é usado para alocação dinâmica de memória. Tenha em mente que tanto o Heap como a Pilha são armazenados na RAM.
Mais sobre o Heap Memory
Java heap memory é usado pela máquina virtual para alocação de objetos. Sempre que você cria um objeto, ele é sempre criado no heap. Máquinas virtuais, como JVM ou DVM, fazem a coleta de lixo (GC) regular, tornando a memória heap de todos os objetos que não são mais referenciados disponíveis para alocações futuras.
Para proporcionar uma experiência suave ao usuário, o Android define um limite rígido no tamanho da heap para cada aplicação em execução. O limite de heap size varia entre os dispositivos e é baseado na quantidade de RAM que um dispositivo tem. Se o seu aplicativo atingir este limite de heap e tentar alocar mais memória, ele receberá um OutOfMemoryError
e terminará.
Pergunte-se qual é o tamanho do heap para o seu aplicativo?
Vamos descobrir isso juntos. Em andróide, temos o Dalvik VM (DVM). O DVM é uma máquina virtual Java única, otimizada para dispositivos móveis. Ele otimiza a máquina virtual para memória, duração da bateria e desempenho, e é responsável por distribuir a quantidade de memória para cada aplicação.
Vamos falar sobre duas linhas no DVM:
- dalvik.vm.heapgrowthlimit: Esta linha é baseada em como o Dalvik vai começar no tamanho da pilha da sua aplicação. É o tamanho padrão do heap size para cada aplicação. O máximo que sua aplicação pode alcançar!
- dalvik.vm.heapsize: Esta linha representa o tamanho máximo do heap size para um heap maior. Você pode conseguir isso pedindo ao androide para uma pilha maior no manifesto da sua aplicação (android:largeHeap=”true”).
Não use uma pilha maior na sua aplicação. Faça isso SOMENTE se você souber exatamente o efeito colateral deste passo. Aqui eu lhe darei informações suficientes para continuar pesquisando o tópico.
Aqui está uma tabela mostrando qual o tamanho do heap que você tem baseado na RAM do seu dispositivo:
+==========================+=========+=========+===================+
| DVM | 1GB RAM | 2GB RAM | 3GB RAM OR HIGHER |
+==========================+=========+=========+===================+
| DEFAULT(heapgrowthlimit) | 64m | 128m | 256m |
+--------------------------+---------+---------+-------------------+
| LARGE(heapsize) | 128m | 256m | 512m |
+--------------------------+---------+---------+-------------------+
Recorde quanto mais ram você tiver, maior será o tamanho do heap. Tenha em mente que nem todos os dispositivos com maior ram vão acima de 512m faça a sua pesquisa no seu dispositivo se o seu dispositivo tem mais de 3GB para ver se o seu tamanho da pilha é maior que 512m.
Como pode verificar o tamanho da pilha do aplicativo para o seu dispositivo?
Usando o ActivityManager. Você pode verificar o tamanho máximo da pilha em tempo de execução usando os métodos getMemoryClass()
ou getLargeMemoryClass()
(quando uma pilha grande está ativada).
- getMemoryClass(): Retorna o tamanho máximo padrão do heap.
- getLargeMemoryClass(): Retorna o tamanho máximo de pilha disponível depois de ativar a bandeira de pilha grande no manifesto.
ActivityManager am = getSystemService(ACTIVITY_SERVICE);
Log.d("XXX", "dalvik.vm.heapgrowthlimit: " + am.getMemoryClass());
Log.d("XXX", "dalvik.vm.heapsize: " + am.getLargeMemoryClass());
Como funciona no mundo real?
Usaremos esta aplicação simples para entender quando usamos a pilha e quando usamos a pilha.
A imagem abaixo mostra uma representação da pilha e da pilha da aplicação e onde cada objeto aponta e é armazenado quando executamos a aplicação.
>>
Vamos rever a execução da aplicação, paramos cada linha, explicamos quando a aplicação aloca os objectos, e armazenamo-los na pilha ou na pilha. Veremos também quando a aplicação libera os objetos da pilha e do heap.
>
- Linha 1 – A JVM cria um bloco de memória da pilha para o método principal.
- Linha 2 – Nesta linha, criamos uma variável local primitiva. A variável será criada e armazenada na memória da pilha do método principal.
- Linha 3 – Aqui preciso da vossa atenção!! Nesta linha, nós criamos um novo objeto. O objeto é criado na pilha do método principal e armazenado na pilha. A pilha armazena a referência, o endereço de memória do objeto (ponteiro), enquanto a pilha armazena o objeto original.
>
>
>
- >
- Linha 4 – O mesmo da linha 3.
- Linha 5 – A JVM cria um bloco de memória de pilha para o método foo.
- Linha 6 -Criar um novo objecto. O objeto é criado na memória da pilha do método foo, e nós armazenamos na pilha o endereço da memória da pilha do objeto que criamos na Linha 3. O valor (endereço da memória heap do objeto na linha 3) que passamos na linha 5. Tenha em mente que Java sempre passa referências por valor.