Neste primeiro artigo sobre o Garbage Collector vamos ver alguns conceitos importantes para entender o funcionamento deste gerenciador de memória, um dos pilares do .NET Framework.
Quando programamos em C ou C++, muitos objetos requerem que façamos a alocação de seus recursos em sua declaração, antes dos objetos poderem ser utilizados com segurança. Liberar estes recursos de volta ao pool de memória livre também é responsabilidade do programador. Se os recursos não forem liberados, dizemos que o código está com vazamento de memória e mais recursos serão consumidos sem necessidade. Por outro lado, se os recursos forem liberados prematuramente, perda de dados, áreas de memória corrompidas e erros de ponteiros nulos podem ocorrer.
Java e C# previnem estes perigos gerenciando independentemente o tempo de vida de todos os objetos em uso pelo aplicativo.
No Java, o JVM toma conta de liberar memória não utilizada mantendo registro de referências para os recursos alocados. Assim que o JVM detecta que um recurso não é mais referenciado, o recurso é coletado e jogado fora.
Em C#, o Garbage Collector é responsabilidade da CLR (Common Language Runtime) com funcionamento parecido com o JVM. O Garbage Collector checa periodicamente o heap de memória por qualquer objeto sem referência e libera os recursos vinculados a esses objetos.
Coleta Automática (Garbage Collector) no .NET
Quando a Microsoft decidiu por uma nova geração de plataforma chamada .NET, sua primeira intenção foi criar uma linguagem fácil de aprender e ter um grande conjunto de APIs. Houve um grande esforço também na coleta de lixo (Garbage Collector) através deste modelo de coleta automática de lixo.
O Garbage Collector foi implementado em uma thread em separado, sempre rodando no back end.
Mas rodar uma thread separada não dá overhead extra? Sim, com certeza. É por isso que a thread do Garbage Collector tem a prioridade mais baixa. Mas quando o Sistema não encontra mais espaço no heap gerenciado, a thread do Garbage Collector recebe prioridade REALTIME (é a mais alta prioridade no Windows) e sai recolhendo todos os objetos que não são mais utilizados.
Benefícios do Garbage Collector
- Você pode desenvolver seu aplicativo sem se preocupar em liberar a memória toda vez que terminar de utilizar um determinado objeto.
- A alocação de objetos no heap gerenciado é feito com o máximo de eficiência.
- Recolhe objetos que não estão sendo utilizados e limpa a memória.
- Mantém os objetos em memória em seu devido lugar, impedindo que um objeto utilize conteúdo de outro objeto.
O heap gerenciado
Depois que o Garbage Collector é inicializado pelo CLR (Commom Language Runtime), ele aloca um segmento de memória para armazenar objetos. Esta memória é chamada de heap gerenciado.
Existe um heap gerenciado para cada processo gerenciado. Todas as threads de um processo alocam memória no mesmo heap.
Observação Técnica: O tamanho dos segmentos alocados pelo Garbage Collector é específico de sua implementação e está sujeita a alterações a qualquer momento, incluindo em atualizações periódicas. Seu aplicativo nunca deve fazer suposições sobre ou depender do tamanho de um segmento específico. Muito menos tentar configurar a quantidade de memória disponível para alocação.
Quanto menos objetos alocados no heap, menos trabalho o Garbage Collector terá. Por isso, quando você alocar objetos, tente alocar apenas a quantidade de memória necessária.
Quando uma coleta é disparada, o Garbage Collector limpa a memória ocupada por objetos sem uso. Este processo “compacta” objetos em uso, os reorganiza para ficarem juntos e o espaço excedente é removido. Assim o heap fica menor, ocupando menos memória. Isto assegura que objetos alocados juntos continuem juntos no heap gerenciado, preservando sua localização.
A frequência e duração da intervenção das coletas é resultado do volume de alocações e da quantidade de memória restante no heap gerenciado.
O heap é dividido em dois: uma parte com objetos grandes (a partir de 85Kbytes, normalmente arrays) e outro com os menores.
Gerações
O heap é organizado em gerações para poder lidar com objetos de vida útil longa e objetos de vida útil curta. A coleta ocorre primeiro com os objetos de vida útil curta, que normalmente ocupam uma pequena parte do heap. Existem 3 gerações de objetos no heap:
- Geração 0: A mais jovem das gerações e contém objetos de vida útil curta. Um exemplo de objeto de vida curta são variáveis temporárias. A coleta ocorre mais frequentemente nesta geração.
Objetos novos alocados formam uma nova geração de objetos e são implicitamente de geração 0, a não ser que sejam objetos grandes. Neste caso eles vão diretamente para o heap de grandes objetos na geração 2.
A maioria dos objetos são coletados na geração 0 e não sobrevivem para a próxima geração.
- Geração 1: Esta geração contém objetos de vida curta e serve como um buffer entre objetos de vida curta e objetos de vida longa.
- Geração 2: Contém objetos de vida longa. Um exemplo deste tipo é um objeto de uma aplicação servidor que contém dados estáticos que se mantém ativos durante o processo.
A coleta ocorre em gerações específicas quando as condições permitem. Coletar uma geração significa coletar objetos nesta geração e em todas as gerações mais novas. Uma coleta na geração 2 também é conhecida como uma coleta total, pois recolhe todos os objetos de todas as gerações (ou seja, todos os objetos do heap gerenciado).
Sobreviventes e promoções
Objetos não recolhidos durante uma coleta são conhecidos como sobreviventes e são promovidos para a próxima geração. Objetos sobreviventes de uma coleta na geração 0 são promovidos para a geração 1, objetos sobreviventes de uma coleta na geração 1 são promovidos para a geração 2 e objetos que sobreviventes a uma coleta na geração 2 permanecem na geração 2.
Quando o Garbage Collector percebe que a taxa de sobrevivência em uma geração é alta, ele aumenta o limite de alocações para esta geração, de modo que a próxima coleta consegue uma quantidade substancial de memória recuperada. O CLR faz o balanço continuamente de duas prioridades: não deixar que o conjunto de trabalho de um aplicativo fique muito grande e não deixar a coleta tomar muito tempo.
Gerações e segmentos efêmeros
As gerações 0 e 1 também são conhecidos como gerações efêmeras, pelo fato dos objetos neles serem de vida curta.
Gerações efêmeras devem ser alocados no segmento de memória que é conhecido como segmento efêmero. Cada novo segmento adquirido pelo Garbage Collector se torna um novo segmento efêmero e contém os elementos que sobreviveram a coleta da geração 0. O antigo segmento efêmero se torna o novo segmento geração 2.
O tamanho do segmento efêmero varia dependendo do sistema (32 ou 64 bits) e do tipo de Garbage Collector que está em execução. Valores padrão estão na tabela seguinte.
32-bit | 64-bit | |
GC de estação | 16 MB | 256 MB |
GC de servidor | 64 MB | 4 GB |
GC de servidor com mais de 4 CPUs lógicos | 32 MB | 2 GB |
GC de servidor com mais de 8 CPUs lógicos | 16 MB | 1 GB |
Segmentos efêmeros podem incluir objetos geração 2. Objetos geração 2 podem utilizar múltiplos segmentos (quantas seu processo precisar e a memória suportar).
A quantidade de memória liberada é proporcional ao espaço ocupado pelos objetos inativos.
Agora que estudamos um pouco dos conceitos do Garbage Collector, conseguiremos entender melhor seu funcionamento no próximo artigo da série.
Meu e-book Como Aprender a Programar do Absoluto Zero está GRATUITO por tempo limitado!
Olha o link: 👉🏼 http://celsokitamura.com.br/como-aprender-a-programar
Bora aprender a programar!