VB .NET  -  Otimizando o Garbage Colector - GC.


O coletor de lixo (garbage collector) conhecido também como gc, é usado para retirar objetos instanciados na memória mas que não estão sendo mais usados(referenciados). Este recurso, disponível na Linguagens .NET permite que você otimize o seu código, deixando ele mais rápido, e com isso ganhando produtividade O que o gc efetua é na verdade um gerenciamento automático de memória que automaticamente libera blocos de memória que não serão mais usados em uma aplicação.

Algumas estratégias de alocação de memória:

–Estática: As áreas de memória são alocadas antes do início do programa; não permite mudanças nas estruturas de dados em tempo de execução (Ex: Fortran)
–Linear: memória alocada em fila ou em pilha; não permite remoção de objetos fora da ordem de criação (Ex: Forth)
–Dinâmica: permite liberdade de criação e remoção em ordem arbitrária; requer gerência complexa do espaço ocupado e identificação dos espaços livres (ex: Java, C++,C#,.NET)

As vantagens que podemos apontar em usar linguagens que disponibilizam este recurso como Java, C# e VB.NET são:

Mas nem tudo são flores pois o gc não é perfeito.

O processo de funcionamento do Garbage Collector, daqui pra frente chamado apenas de coletor ou gc, da plataforma .NET pode parecer a primeira vista misterioso e obscuro, uma caixa preta que funciona por sozinha e a qual parece que não temos acesso algum. Poderia um programador exercer algum controle sobre o processo de funcionamento do coletor de lixo ?

A resposta é : sim pode !

Existem situações nas quais o programador vai ter que executar o coletor de forma explicita chamando um dos métodos expostos pelo gc. Uma destas situações seria, por exemplo, após a criação de uma grande quantidade de objetos que foram usados para um fim específico e que não serão mais necessários. Neste caso porque esperar o gc entrar em ação se você pode fazer a faxina ? O gc funciona automaticamente mas você pode invocá-lo manualmente; desta forma, para destruir um objeto imediatamente você pode simplesmente digitar: System.GC.collect()

O objeto System.GC expõe diversos métodos que podem ajudá-lo ano processo de coleta do lixo, seja para um objeto específico ou para um sistema como um todo.

Quando você termina de usar um objeto atribuindo-lhe o estado como Nothing ou deixando-o de utilizar ele é incluído no sistema de coleta de lixo para uma eventual finalização e liberação. A finalização ocorre quando o método Finalize() do objeto é chamado. A liberação ocorre quando a memória alocada para o objeto é reclamada e disponibilizada para ser usada por outro processo gerenciado ou não gerenciado.

O coletor de lixo atua em ondas ou ciclos de geração. Quando um objeto entra pela primeira vez no sistema, ele aparece na Geração 0. Se depois de algum tempo o objeto ainda não foi finalizado ou liberado , ele é movido para a próxima geração, Geração 1. Nem todas as plataformas suportam este tipo de atuação. Para verificar você pode usar a propriedade System.GC.MaxGeneration e assim determinar a geração máxima de vida e um objeto. Para sistemas que não suportam este tipo de atuação o valor retornado será sempre igual a zero.

Você pode usar um dos seguintes membros do System.GC para ajudá-lo a gerenciar o sistema de coletor de lixo em aplicações com memória crítica:

Método Descrição
AddMemoryPressure()
RemoveMemoryPressure()
O sistema de coletor de lixo esta relacionado somente com a memória gerenciada que é a memória alocada através de características da plataforma .NET. A memória não gerenciada não entra no processo do coletor. No entanto o processo de coleta toma a quantidade total de memória, a gerenciada e a não gerenciada, em conta para determinar como rapidamente liberar recursos.

O método AddMemoryPressure() aceita como argumento um contador byte e diz para o coletor de lixo agir como se a quantidade de memória não gerenciada estivesse alocada. Dependendo do tamanho da pressão, o processo de coleta será processado de forma diferente devido as mudanças percebidas na memória disponível.

Você precisa posteriormente reverter a pressão de alocação através do método RemoveMemoryPressure() usando o mesmo contador byte fornecido com a requisição de pressão inicial. Você pode ter diversas requisições de pressão ativas ao mesmo tempo.
Collect Este método força a coleta imediata (finalização e liberação) do lixo. Por padrão, este método coleta o lixo em todas as gerações. Você também pode passar o número da geração, e a coleta será feita somente entre a geração 0 e o número da geração informado como argumento.
CollectionCount Retorna um o número de vezes que o lixo foi coletado para um número de geração específico. O número de geração é passado como argumento
GetGeneration Se você tiver acesso a referência do objeto que já entrou no sistema de coleta, passando-o como um argumento para GetGeneration() , será retornado o número da geração no qual o objeto aparece.
GetTotalMemory Retorna uma estimativa do total da memória gerenciada alocada. Aceita um argumento booleano que se for True permite ao gc ocorrer antes que a estimativa seja calculada.
KeepAlive Normalmente , quando um objeto não esta mais referenciado , você não se importa quando o processo do gc irá destruí-lo. Contudo , se você alocar memória gerenciada que você deseja compartilhar com um processo externo não gerenciado (como uma função em uma DLL ActiveX), e este processo irá usar a memória além do seu uso local, o gc retardará processamento do objeto até ele não mais existir.

Este método ajuda a forçar um retardamento neste processo do gc. Para usar o método você passa a ele uma referência do objeto que deseja reter e você chama este método quando você não deseja mais retê-lo. KeepAlive mantém o objeto vivo até um ponto , a partir deste ponto ele vai para o gc.
SuppressFinalize
ReRegisterForFinalize
Passando uma referência de um objeto para SuppresFinalize() diz ao gc para não chamar o método Finalize() deste objeto antes de liberar o objeto. Este método é mais usado com objetos que implementam a interface System.IDisposable. Se voce limpar todos os recursos alocados durante a chamada a Dispose(), não haverá mais nada para finalizar, chamando SuppressFinalize() você não precisa chamar Finalize().

Se você usar este método mas depois descobrir que você precisa habilitar novamente o processo de finalização para um objeto , chame o método ReRegisterForFinalize.
RequestFinalizeOnShutdown Método de trabalho implementado para se chamar um rotina de finalização.
WaitForPendingFinalizers Este método suspende a execução da aplicação até que todos os objetos relevantes no gc tenham o seu método finalize() chamados.
MaxGeneration Lista as gerações que o sistema pode suportar.Se não suportar exibe o valor 0.
TotalMemory Exibe o espaço total de byte de objetos referenciados, e podem sobrepor objetos que serão lançados em breve

Nota:  Quando você aciona System.gc(), não significa que o garbage collector entrará em ação, é apenas uma sugestão que você pode fazer a plataforma .NET;o GC não será executado em situações críticas do sistema, como falta de recursos, etc.

Vejamos a seguir dois exemplos de como você pode usar a GC. Todos os exemplos abaixo foram feitos no VB 2005 Express.

1- Forçar uma coleta

Abra o VB 2005 Express e crie um novo projeto do tipo Console Application com o nome de forcaGC

A partir do menu Project selecione a opção Add Class e informe o nome forcaGC.vb e a seguir inclua o seguinte código na classe:

Public Class classePrincipal

Shared Sub Main()
   Dim obj As MeuObjeto = New MeuObjeto
   obj = Nothing
  
 'estou forçando uma coleta no objeto após liberá-lo
   GC.Collect()
End Sub
End Class
-----------------------------------------------------------------------------------
Public Class MeuObjeto
  ' Construtor do objeto - É chamado quando o objeto é instanciado (new)
  Public Sub New()
   Console.WriteLine("Objeto " & GetHashCode() & " criado.")
  End Sub

Protected Overrides Sub Finalize()
 
  ' Finalize - chamado quando o objeto é removido da memoria...
   MyBase.Finalize()
   
' avisa o usuário que vamos destruir o objeto
   Console.WriteLine("Objeto " & GetHashCode() & " finalizado.")
End Sub
End Class

O resultado da execução do código ao lado mostra o objeto criado e o mesmo
objeto finalizado.

Perceba que na classe estática (Shared) Main() o objeto é criado via palavra chave New.

Desta forma o seu construtor é iniciado exibindo a mensagem de criação do objeto.

Em seguida, na classe Main() , o objeto é liberando pela atribuição de Nothing ao mesmo.

Em seguida forçamos o GC a entrar em ação e executar o método Collect().

Isto faz com que o método sobrescrito (Overrides) Finalize seja chamado destruindo o objeto.
 

 

2- Exibindo a geração de objetos

Abra o VB 2005 Express e crie um novo projeto do tipo Console Application com o nome de generationGC.

A partir do menu Project selecione a opção Add Class e informe o nome generationGC.vb e a seguir inclua o seguinte código na classe:

Public Class Principal


   Shared
Sub Main()

      'cria uma instância de Object

         Dim meuObjeto As Object = New Object()

      Dim i As Integer
 

      For i = 0 To 3

        Console.WriteLine(String.Format("Geração = {0}",GC.GetGeneration(meuObjeto)))                 

        'força a coleta

        GC.Collect()

       'aguarda que o objeto seja finalizado

       GC.WaitForPendingFinalizers()

    Next i

End Sub

End Class

 

Com isto apresentei o GC a você , aguarde em breve mais artigos sobre este assunto...

Até o próximo artigo VB.NET


José Carlos Macoratti