A CLR - o ambiente de execução do .NET Framework - é um ambiente gerenciado e, por isso, não precisamos nos preocupar diretamente com questões relacionadas a liberação de memória. Isso é possível por causa de um componente importantíssimo da CLR: trata-se do garbage collector. O garbage collector tem exatamente a responsabilidade de lidar com a liberação da memória associada a uma variável e/ou objeto quando este não é mais utilizado em nenhum ponto do nosso código.
Entender como o garbage collector funciona no .NET é essencial para que possamos entender como um todo como a CLR funciona. Questões relacionadas ao garbage collector também são muito úteis quando precisamos escrever código de alta performance para aplicações que exigem respostas próximas ao real-time “de verdade”. Por isso, vamos entender como o garbage collector funciona na CLR.
Como funciona o gerenciamento de memória no .NET?
No artigo C# Gerenciamento de memória no C#: stack, heap, value-types e reference-types que escrevi aqui para o blog da TreinaWeb, apresentei as duas principais divisões da área de memória durante a execução de uma aplicação .NET: a stack, para os value-types; e a heap, para os reference-types. Dependendo do tipo de “dado” que você aloca em uma variável, o dado pode ser deslocado ou para a stack - se estivermos falando de structs - ou para a heap - se estivermos falando de tipos ditos “complexos”, como objetos.
Formação Desenvolvedor C#
Conhecer a formaçãoQuando falamos sobre o garbage collector, é importante frisar que este atua organizando a memória na heap, embora ele utilize as referências guardadas na stack para determinar o que ainda está em uso ou não. Os objetos que são alocados na heap acabam sendo divididos em três grupos, também chamados de gerações:
- geração 0: é a primeira geração onde um objeto é alocado. Assim que algo é alocado na heap,o objeto alocado é colocado imediatamente como sendo parte da geração 0;
- geração 1: trata-se de uma geração de transição, onde objetos que são utilizados de maneira “média” são alocados;
- geração 2: é uma geração que contém objetos que são utilizados por mais tempo e que, por isso, precisam existir na memória por um tempo maior.
O que o garbage collector faz basicamente é migrar os objetos entre estas três áreas distintas e eliminar as áreas de memória associadas a objetos que não são mais utilizados.
O ciclo de trabalho do garbage collector na heap
O garbage collector trabalha em ciclos de análise sobre as gerações que existem na heap. O que diferencia o funcionamento do garbage collector sobre as gerações é a peridiocidade da inspeção: a geração 0, por ser menor na maioria das vezes, sofre análises do garbage collector mais frequentes do que as gerações 1 e 2 por exemplo. Nestas análises, o que o garbage collector faz é verificar se os integrantes das gerações estão ainda sendo utilizados e, caso algum integrante não seja mais necessário, este é removido, fazendo com que a área de memória correspondente seja liberada e fique disponível para novas alocações.
É importante notar também que os ciclos de análise ocorrem na geração que é alvo do ciclo e nas gerações anteriores. Por exemplo: se o garbage collector precisa analisar a geração 0, somente ela é analisada. Se o garbage collector precisa analisar a geração 1, as gerações 1 e 0 são analisadas. Se o garbage collector precisa analisar a geração 2, as gerações 2, 1 e 0 são analisadas. Em decorrência desse funcionamento, o ciclo de análise na geração 2 ganha o nome de coleta completa, pois todas as gerações acabam sendo analisadas.
Essa análise do garbage collector pode ocorrer em situações pré-definidas:
- Quando o sistema operacional informa que possui pouca memória física disponível;
- O tamanho das gerações é estourado;
- O método
GC.Collect()
é invocado, o que caracteriza uma chamada explícita para o processo de análise do garbage collector dentro da aplicação.
Promoções de geração em objetos
Os objetos são inicialmente alocados na geração 0 - portanto, na geração que sofre coletas mais rápidas - porque a CLR supõe que estes objetos não serão mais necessários muito rapidamente, o que faria com que estes objetos fossem removidos rapidamente da memória. E isso é verdade para a maioria dos cenários. Porém, alguns objetos podem precisar sobreviver por mais de um ciclo de análise do garbage collector… Quando o garbage collector detecta um objeto que precisa sobreviver por mais tempo na memória do que o esperado para a geração onde ele se encontra, o objeto sofre o que é chamado de promoção: o objeto é deslocado para a geração superior, sofrendo ciclos mais espaçados de análise do garbage collector. Por exemplo: se um objeto que está na geração 0 sobrevive a um ciclo de análise, o mesmo é deslocado para a geração 1. Se um objeto sobrevive a um ciclo de análise sob a geração 1, o mesmo é deslocado para a geração 2, sofrendo a análise do garbage collector de maneira mais espaçada ainda.
Esse processo de promoção pode tornar a gerência de memória um processo muito lento, já que esse deslocamento de gerações pode exigir uma quantidade de processamento computacional considerável, dependendo da quantidade de objetos alocados… Por isso, o CLR sempre está “supervisionando” o trabalho do garbage collector. Se o garbage collector passa a detectar que a taxa de sobrevivência de objetos em uma determinada geração é muito alta, a CLR aumenta o tamanho da geração em questão, evitando que as gerações tenham seu tamanho estourado frequentemente. A CLR sempre tenta equilibrar os ciclos de análise do garbage collector e o tamanho das gerações - as gerações também não podem ser sempre expandidas, pois isso deixaria o sistema operacional sem memória em um curto espaço de tempo.
Áreas efêmeras
O CLR e o garbage collector supõe que os objetos que fazem parte das gerações 0 e 1 terão um ciclo de vida muito curto, sendo eliminados rapidamente da memória. Por isso, estas gerações também são chamadas de gerações efêmeras.
As gerações também costumam ser agrupadas em segmentos de memória, segmentos estes que são gerenciados pelo garbage collector. Segmentos que possuem as gerações 0 e 1 também são chamados de segmentos efêmeros.
O garbage collector também realiza a manipulação destes segmentos. Os segmentos recém-criados também são definidos como segmentos efêmeros. Quando o garbage collector cria mais um novo segmento, o segmento que antes era efêmero deixa de ter essa característica, passando a ser considerado um segmento de geração 2 (a geração com objetos que sobrevivem por mais tempo). O novo segmento passa a ser considerado neste momento como o segmento efêmero.
No próximo artigo, iremos analisar em detalhes como o garbage collector realiza o trabalho de remoção e promoção de objetos entre os segmentos gerenciados pelo garbage collector.