Olá pessoal.

O Garbage Collector está longe de ser novidade, mas ainda hoje há poucos desenvolvedores que conhecem o seu funcionamento além do simples fato de saber da sua existência. Com o objetivo de explicar resumidamente o assunto resolvi criar este post no blog, inclusive como sendo uma introdução para posts subsequentes sobre .net debugging e throubleshooting. Então, boa leitura.

 

No desenvolvimento em plataforma Microsoft antes do .NET Framework (C++, Delphi, VB6, etc) era muito comum termos problemas de “memory leak”, sendo necessário fechar o aplicativo ou mesmo reiniciar a máquina para que a memória fosse “liberada” e pudesse ser utilizada por outros aplicativos. Quando falamos em aplicativos Desktop, o impacto era menor pois muitos usuários desligavam suas máquinas no final de um dia de trabalho e no dia seguinte a ligavam novamente, voltando com toda a memória disponível. Com a migração dos sistemas para os servidores, trabalhando 24x7x365, o “memory leak” tornou-se algo realmente crítico pois impacta um grande número de pessoas ao mesmo tempo e o próprio negócio da empresa.

O Garbage Collector (GC) tem um papel fundamental que é gerenciar a alocação e liberação de recursos, principalmente memória. Com isso, o GC assumiu uma responsabilidade que antes era do desenvolvedor (e que nem todos faziam direito) e passou a controlar quando os recursos deveriam ser liberados. Minimizando, mas não eliminando por completo a ocorrência de “memory leak”.

No .NET Framework há um GC para cada AppDomain, ou seja, há pelo menos um GC em execução para cada processo/aplicativo .NET sendo um independente do outro.
Ou seja, o GC não é um serviço centralizado no Windows que avalia e coleta os dados de todos os aplicativos mas sim uma thread em funcionamento em cada aplicativo gerenciado. Desta forma, ao encerrar um aplicativo toda a memória alocada (recursos gerenciados) pelo GC para este aplicativo é liberada junto.

Quando o GC entra em ação?

  • Quando há pouca memória física;
  • Quando a quantidade de memória alocada ultrapassa um limite (que é móvel e auto ajustável);
  • Quando o GC.Collect é chamado (não recomendado);
  • Quando o aplicativo ou AppDomain é encerrado.

A frequência e a duração da execução do GC depende do volume de alocação e tempo que estes objetos permanecem “vivos” para o aplicativo.

Gerações
Para fazer o trabalho da melhor forma possível, o GC divide a área de alocação em memória (heap) em três “generations”, são elas:

  • Generation o (zero): contém objetos de vida curta e de tamanho menor que ~85KB, por exemplo, uma variável string temporária. Basicamente todo novo objeto alocado fica nesta geração.
  • Generation 1: contém objetos de vida curta que estavam na Gen0 e continuam sendo usados após uma coleta do GC. Ou seja, objetos “promovidos” da Gen0 para a Gen1.
  • Generation 2: contém objetos de vida longa. Por exemplo, objetos maiores que ~85kb, objetos estáticos e objetos promovidos da Gen1.

A coleta da Gen0 e Gen1 ocorre com maior frequência que a Gen2, e na prática geralmente é suficiente para liberar a maioria dos objetos de um aplicativo.
A coleta dos Gen2 é conhecida como “Full Garbage Collection” pois verifica os objetos em todas as gerações e obviamente é uma coleta mais demorada, e que por isso ocorre com menos frequência. É possível monitorar a coleta através de contadores de performance do Windows (perfmon.exe), onde o normal seria ter uma coleta na Gen2 para cada dez coletas na Gen0.

Por hoje era isso, caso queira saber mais sobre o GC consulte “Fundamentos do Garbage Collector”.

Em breve mais artigos sobre o GC e também como identificar problemas como alto consumo de memória e baixa performance.

Até mais.

Rafael Leonhardt