Garbage Collector (GC) – Gerenciamento de memória
Postado por Carlos Fernando Sylverio | Postado em Programação | Postado em 21-01-2010
0
O Framework.NET trouxe alguns benefícios referentes a utilização e gerenciamento de memória, permitindo mais liberdade de alocação e remoção de variáveis na memória em ordem aleatória, executado por meio de uma gerência complexa do espaço ocupado e identificação dos espaços livres.
Dessa forma vida de nós programadores se tornou muito mais fácil, pois não precisamos nos preocupar com o gerenciamento de memória (nada de malloc e free utilzados em C/C++), com isso temos menos erros relacionados ao vazamento de memória, bugs de ponteiros, entre outros.
O principal componente que realiza esse gerenciamento e liberação de memória é o Garbage Collector (Coletor de Lixo ou GC) existente na arquitetura do Common Language Runtime (CLR) e tem como princípio atividade de funcionamento:
- Determinar quais objetos não mais será utilizado, ou seja, não estão mais acessíveis na aplicação;
- Liberar os recursos (memória) utilizados por esses objetos
Entendendo o funcionamento do Garbage Collector
Basicamente o GC divide a memória disponível em 3 áreas distintas:
- Maneged Heap
- Pilha
- Unmanaged Heap
O GC em conjunto com a CLR por meio de diversos algoritmos executa o gerenciamento da área de memória managed heap, que aloca reference types. Enquanto a pilha é utilizada para alocar value types. A unmanaged heap é utilizada para armazenar recursos não gerenciados de forma automática ou recursos nativos.
Mais detalhe da arquitetura do GC ficará para um próximo post, pois irá envolver alguns temas ainda não tratados.
Recursos Não Gerenciados
Um ponto importante é controle de recursos não gerenciados (recursos nativos, que pode ser um arquivo, janela, conexões de banco de dados, sockets, Win32, entre outros).
Implicitamente o Framework.NET mantém um controle das instâncias utilizadas pela aplicação, porem o mesmo não se repete para recursos não gerenciados. Para recursos não gerenciados deve-se fornecer uma maneira de liberar os recursos da memória depois que o aplicativo tiver terminado de usá-los, ou seja, deve ser um processo manual (codificado) executado pelo programador.
Há duas maneiras de se finalizar um recurso não gerenciado:
- Implicitamente utilizando o método Finalize();
- Explicitamente utilizando o método Dispose() do IDisposable;
Observação: Por padrão a Microsoft recomenda a implementar as duas formas de finalizar o objeto. Sendo que o método Finalize() serve de garantia de execução de liberação de memória impedindo que o recurso fique permanentemente vazando caso algum programador esqueça de chamar o método Dispose().
A primeira impressão que temos ao ver o finalizador (redefinindo ao método Finalize de System.Object) existente no .NET é que este atua como um destruidor (destructor) presente em linguagens com C/C++ é que são similares. Porem não se iluda pela aparência, pois são bem diferentes em suas características.
Caracteristicas do Finalizador:
- Execução nem sempre é não garantida;
- Execução não determinística (instante em que o método é executado não é conhecido);
- Chamada ao método ocorre desde que não existam ciclos infinitos;
- Quando chamado shutdown, sua execução só ocorre caso seu tempo de execução não seja superior a 2 segundos e o somatório não exceda 40 segundos (tempos aproximados);
Em resumo o CLR não garante a ordem de chamada dos métodos Finalize().
Exemplo de implementação do finalize:
Código C#.NET:
1 2 3 4 5 6 7 8 9 | public class AlgumaClasse { … ~AlgumaClasse() // finalizador { // código de limpeza } … } |
Codigo gerado pelo compilador:
1 2 3 4 5 6 7 8 9 10 | public class AlgumaClasse { … protected override void Finalize() { try { /* código de limpeza */ } finally { base.Finalize(); } } … } |
O método Dispose é uma implementação de um padrão conhecido como “padrão de descarte” e impõe uma ordem na vida de um objeto.
O método Dispose() deve liberar os recursos que ele possui assim como os recursos de propriedade de seus tipos base. Esse processo é executado através de uma hierarquia de tipos de base, ou seja, cada objeto irá chamar o método Dispose() da classe estendida ou implementada. Garantindo assim que os recursos são sempre limpos adequadamente.
Um característica particular do método Dispose() é que este pode ser chamado várias vezes sem lançar exceção.
É importante salientar que não há benefícios de desempenho na utilização do método Dispose() a objetos gerenciados pela CLR (tais como Arrays). Este método deve ser utilizado em objetos que utilizam recursos nativos e objetos COM que são expostas ao Framework.NET, como exemplo podemos citar a classe FileStream que implementa a interface IDisposable.
Exemplo de implementação do métiodo Dispose():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | // Design pattern para uma classe base. public class Base: IDisposable { //Implementação da interface IDisposable. public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { // libera outros estados (managed objects). } // Libera sus próprios estados(unmanaged objects). // Define campos grandes como null. } // Sintaxe para finalização do código. ~Base() { //Chamada simples Dispose(false). Dispose (false); } } // Design pattern para a classe derived. public class Derived: Base { protected override void Dispose(bool disposing) { if (disposing) { // Liberação de recursos gerenciados } // Liberação de recusos não gerenciados. // Define campos grandes como null. // Chama Dispose da classe base. base.Dispose(disposing); } // A classe derived não tem o método Finalize // ou um método Dispose com parametro pois herda da classe base. } |
Enjoy






seguidores
leitores