[ Contéudo ] Como o Garbage Collector funciona?
Introdução:
Esse é um assunto que vejo sendo pouco comentado nas comunidades de programação voltadas para o JavaScript... E pouco se fala sobre vazamento de memória
que é o Garbage Collector ameniza.
Um dia desses eu fiz uma pergunta por aqui, infelizmente eu não tive respostas sobre vazamentos de memória. Obviamente pesquisei mais, porém, pouco encontrei. Entretanto, achei um conteúdo bem interesse que elucida muito bem sobre Garbage Collector e como evita vazamento de memória de forma mais apropriedade.
Observações:
No final, vou deixar o site que citei como fonte. O que eu quero enfatizar é que eu não necessariamente vou traduzir o artigo e sim expor o que aprendi nele, e adapta-lo da forma que eu acredito que seja de mais fácil entendimento.
Existe alguns trechos e exemplos que irei trazer para cá do artigo, pois acredito que melhorará o entendimento, mas somente isso. Como eu disse anteriormente, não é necessariamente uma tradução, e sim uma adapatação para uma forma que eu acredito que seja melhor de entender.
O que é gerenciamento de memória?
Antes de tudo, precisamos entender o que é gerenciamento de memória. Gerenciamento de memória, como o próprio nome já sugeste, é o ato de gerenciar a memória para que não ocorra problemas de perfomance, vazamento de memória, dentre outras causas que não citarei para não tangenciar muito o assunto.
Linguagens de programação como C, C#, dentre outras, possui formas de gerenciar memória de maneira manual. O que significa que o programador vai explicitamente determina o que entra e o que sai da memória e quando isso deve acontecer.
Como já citei algumas vezes, vazamento de mémoria
é um dos principais problemas que ocorrem quando o programador não se preocupa com este problema.
O que é vazamento de memória?
Vazamento de memória é quando alocamos memória, e mesmo quando não estamos mais usando esse valor, ele permance da memória.
É como se abríssemos algo para usar, usamos, e depois que usamos não precisamos mais disso, e é natural que não queiramos que isto continue usando memória, então devo liberar, mas continua lá, alocado na memória.
No exemplo acima eu demonstro um caso muito comum. Temos um evento sem eventos, depois que adicionamos um evento, este evento agora está alocado na memória. Quando utilizamos o evento, ele continua alocado na memória, mesmo quando não estamos utilizando ele (disparando o evento).
Isso quer dizer que devemos remover todos os ouvintes de evento? A resposta é não. Existe memória que deve permancer o tempo todo (memória ativa), mesmo ela não seja usada, depende muito do projeto, mas dando o exemplo de um modal.
Num modal, podemos ter um botão que dispara um evento, mas ai é que tá o problema. O modal só precisa de um ouvinte de evento, quando o modal está ativo, quando fechado, se faz necessário remover este ouvinte de evento para não alocar memória de forma desnecessária. Então ao fechar o modal, você pode adicionar uma instrução junto com a função que o fecha para remover ouvintes e na função que abre adicionar os ouvintes.
Isso foi um exemplo básico sobre o que é vazamento de memória. Lembrando que cada caso é um caso, atente-se as necessidade de seu projeto.
Garbage Collector
Como eu disse um pouco mais acima, toda linguagem de uma forma de gerenciamente de memória, e o JavaScript não é diferente. A diferença é que em linguagens como C
, se faz o gerencimento de forma manual, já no JavaScript isso se faz de forma automática, o que é bom, mas gera alguns problemas.
Algoritmo do Garbage Collector:
- A partir da raiz, o Garbage collector vai analisando tudo e "marca" todos os valores
- Depois, o garbage collector vai em todos os objetos "marcados" e marca sua referência
- Isso só acontece uma vez, então o garbage collector não repete o processo para objectos já marcados
- Depois de visitar tudo que foi marcado, o garbage collector elimina tudo aquilo que não foi marcado.
Ok, falmos de "marcado", de analise e etc... Em resumo, Garbage collector vai analisar a partir da raiz, todos os objetos e marcar eles, para assim, identifica-los posteriomente. Isso tem dois objetivos, marcar objetos para que não repita o processo novamente com eles, e marca-los para saber que o objeto em questão é "acessível", então possui uma referência, assim, ele não será coletado pelo o Garbage collector e eliminado da memória.
Referência:
O conceito fundamental do Garbage collector são referência, pois é com isso que ele determina aquilo que pode ou não ser marcado. Exemplos:
let user = {
name: "John"
};
Aqui user
tem uma referência para um objeto.
A variável global user
faz referência para um objeto. Dito isso, o garbage collector não consegue coletar aquilo que possui uma referência, pois ele julga que: se tem referência, ele ainda é útil para aplicação.
Agora, vamos ver como remover essa referência:
user = null
Se não existe mais nada que aponte para algo (uma referência), então o Garbage collector coleta esse valor e remove da memória, pois se não existe referências, ele não é mais útil.
O problema começa quando temos mais de uma referência:
let user = {
name: "John"
};
let admin = user;
Agora, se fizermos isso:
user = null
O objeto não será apagado, pois admin
ainda faz referência para o objeto! Por isso que ocorre muitos vazamento de memória! Para resolver isso, ambos user
e admin
precisam para de fazer referência para o objeto.
Agora indo para um exemplo ainda mais complexo:
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
Aqui temos um exemplo muito mais complexo que os anteriores, pois possui mais referências. Vamos deletar algumas coisas:
delete family.father;
delete family.mother.husband;
Apagamos a referência de family
para o objeto father
e apagamos a referência para a husband
, agora vai ficar assim:
Apenas o father
foi eleminado pois não existe mais nada que faça referência para ele, mas para os outros objetos, ainda existe referência. então somente uma parte do todo é elminada da memória.
Agora, para eliminar tudo:
family = null;
Como family
é que inicializa a raiz da referência, tendo como um valor null
é o mesmo que cortar tuda a referência fora, pois tudo se inicia de family
.
Conclusão:
Esse foi o fundamental de como o garbege collector se comporta. Ainda possui alguns detalhes que eu não entrei no mérito. Espero que tenham gostado.