Olá pessoal, tudo certin?
Depois de alguma relutância, resolvi iniciar uma nova série “core” aqui no blog. Do que vou tratar? Do fantástico garbage collector. Essa incrível entidade viva e inteligente embutida na CLR e que mantem nossos programas saudáveis e operantes.
O assunto é denso. Tratar dos fundamentos desse “monstro” não é fácil e, por isso, vou fazer as coisas aos poucos. Hoje, por exemplo, vou tentar, apenas, apresentar uma visão simplificada do objeto de trabalho do Garbage Collector: o heap gerenciado.
Espero, de verdade, que vocês gostem dessa nova série. Sintam-se confortáveis para enviar críticas e sugestões.
Sem mais.. Go Code ! … Ops .. hoje não ..
A corrida pelos recursos
Todo programa usa recursos de algum tipo. Sejam:
- arquivos;
- buffers de memória;
- espaço na tela;
- conexões de rede;
- recursos do banco de dados
- …
Em um ambiente orientado a objetos, cada tipo identifica algum recurso disponível que um programa usa. Usar qualquer um desses recursos requer memória para representar o tipo relacionado. Logo, o consumo de qualquer recurso implica, mesmo que minimamente, no consumo de memória.
Como acontece o consumo de recursos
Sempre que nossos programas precisam consumir recursos seguem, necessariamente, as seguintes etapas:
- alocar memória para o tipo que representa o recurso chamando a instrução newobj da Intermediate Language (“gerado” quando usamos o operador new no C#);
- inicializar a memória para receber o estado inicial do recurso e torná-lo disponível (o construtor do tipo é responsável por “iniciar” esse estado);
- usar o recurso acessando os métodos e propriedades do tipo;
- liberar o recurso preparando o clean up (aqui entra, se necessário, o Dispose);
- liberar a memória (aqui, no .net, entra o garbage collector);
Problemas associados ao consumo de recursos
O fluxo, relativamente simples, que acabei de apresentar é uma das maiores fontes de erros de programação. Quantas vezes, em C++, eu esqueci de liberar memória quando esta não era mais necessária?! Quantas vezes tentei usar blocos de memória que já havia liberado?! E você, amigo programador, sente-se confortável respondendo essas perguntas? (acho que não)
Então! Das cavernas da programação de onde eu vim
, problemas associados a essas duas perguntas geravam os dois piores tipos de bug que eu já tive o desprazer de enfrentar. As conseqüências desses erros, geralmente, não eram observáveis imediatamente. Para outros bugs (de outras origens), quando a aplicação não está apresentando o comportamento esperado, era fácil corrigir o problema. Mas, para esses dois tipos de erros, em especial, o problema principal era os famigerados “resource leaks” (com destaque para o consumo burro de memória) e vários objetos corrompidos (dando pouca estabilidade para os meus pobres programas).
Se você também veio das cavernas, já teve que contar com a ajuda do Windows Task Manager, do Process Explorer, do Performance Monitor para encontrar alguns desses bugs .. Arghhhh! Sabe do que estou falando.
Há esperança – há ambientes gerenciados
Em ambientes não gerenciados, a gestão adequada de recursos é uma tarefa difícil e tediosa. Acaba tirando o programador do foco que deveria ser resolver problemas reais. Um artifício que faça algum gerenciamento de memória pelo programador seria fantástico .. felizmente ele existe. Senhores e senhoras, eu apresento o Garbage Collector
O garbage collector tira do programador a responsabilidade de controlar diretamente o consumo de memória e também a liberação dessa. Entretanto, observe que o garbage collector não sabe nada sobre o que está sendo representado na memória e, por isso, o passo 4 ainda não é dos mais simples (aqui entra o Dispose).
Como funciona a alocação de memória para novos objetos no .NET
O CLR requer que todos os recursos sejam alocados em um heap conhecido como managed heap. A experiência de utilização desse heap é semelhante a do heap mantido pelo runtime do C, exceto pelo fato de que não deletamos objetos desse heap – objetos são automaticamente removidos quando a aplicação não precisa mais deles.
Toda a mágica do CLR liberando memória automaticamente sempre me deixou a seguinte questão:: Como o Heap gerenciado sabe quando a aplicação não está mais usando um objeto?…
Como funciona o heap gerenciado no .NET
Há diversas abordagens para coleta de lixo disponíveis hoje em dia. Cada uma apresenta prós e contras e é mais ajustado para determinados ambientes.
No .NET, quando um processo é iniciado, o CLR reserva uma área contínua de endereços de memória, mesmo que sem uso. Esse espaço de endereçamento é o heap gerenciado. O heap também mantém um ponteiro que indica onde o próximo objeto será alocado no Heap. No começo do programa, esse ponteiro aponta para o primeiro endereço (base) da área reservada de memória.
Quando a instrução newobj do intermediate language cria um novo objeto (disparada pelo operador new do C#), são executados os seguintes passos:
- é calculado a quantidade de bytes necessárias para os atributos (variáveis de classe) do tipo, e de suas classes base.
- é acrescentada a esse resultado o overhead associado aos metadados desse objeto (varia conforme versão do framework e processador)
- o clr verifica se a quantidade de bytes necessária está disponível na área reservada (aumentando essa área se for necessário). Se houver memória o suficiente no heap, o objeto irá ocupar esse espaço começando pelo endereço apontado no ponteiro de controle. Logo depois, o contrutor do tipo é chamado (implicitamente passando o endereço do objeto no argumento oculto this) e o newobj retorna o endereço do objeto criado.
Traçando um paralelo com o heap do runtime do C
No heap mantido pelo runtime do C, alocar memória exige um “passeio” através de uma lista encadeada de estruturas de dados que representam a memória para determinar um bloco adequado para um novo objeto. Sempre que um espaço suficientemente grande para o novo objeto é encontrado, este bloco é “reservado e particionado” em uma estrutura de ponteiros mantido na tal estrutura de dados.
Em contraste, no heap gerenciado, a alocação de um objeto significa um deslocamento de um único ponteiro – que é, obviamente mais rápido.
Na prática, alocar memória no heap gerenciado é quase tão rápido quanto alocar espaço no thread stack (para Value Types)! Além disso, muitos heaps (como o do runtime do C) alocam objetos em qualquer lugar onde seja encontrado espaço disponível. O que significa que: se criarmos muitos objetos em sequência é provável que esses objetos estejam separados por “megabytes” de espaços de endereço na memória. No managed heap, os objetos estão, garantidamente, em seqüência.
Em muitas aplicações, objetos alocados em seqüência tendem a ter forte relacionamento uns com os outros. Por exemplo, é bem comum alocar um FileStream e, logo depois, um BinaryWriter. Quando a aplicação usar o BinaryWriter este internamente utiliza o FileStream. No .NET, como esses objetos são armazenados de forma sequencial, há um inquestionável ganho de performance em decorrência do menor esforço para localizar a referência. Especialmente, isso significa que o “espaço de trabalho “ do processo será menor em aplicações gerenciadas do que em aplicações não-gerenciadas. O que tem o excelente efeito colateral de otimizar o caching da CPU potencializando o resultado.
Mas nem tudo são flores…
Nossa! Olhando assim parece que o heap gerenciado é “bem melhor” que o heap mantido pelo runtime do C. Afinal, é tão mais simples, é tão mais rápido. Mas.. Há um pequeno detalhe que deveríamos saeber antes de tanta euforia …
O heap gerenciado tem essas vantages porque ele toma uma “importante” diretiva: o espaço de endereçamento é infinito. Infelizmente, esse não é o caso. O heap gerenciado precisa aplicar algum mecanismo que permita conviver com essa assertiva falsa e esse mecanismo é o Garbage Collector.
Quando uma aplicação chama um “new” para criar um objeto, deveria haver espaço suficiente para acomodar o novo obeto. Se não há, o garbage collector é chamado … mas esse já é assunto para outro post.
Por hoje, era isso… ![]()
Posts relacionados:






Gerson Dias
29/03/2012
Elemar, esqueceu essa série? hehehe continue, fiquei curioso com os próximos capítulos!
abs