Quanto mais programamos Orientado aObjetos, mais temos a necessidade de instanciar componentes sem colá-los na nossa form, ou seja, instanciá-los em runtime, ou de instanciá-los dentro de nossas próprias classes, quando são membros destas.

Isso pode causar diversos memory –leaks causados por quatro fatores:

  1. esquecimento de dar um free no objeto ou componentes sem owner que não são liberados
  2. uma exeption, abort,exit, close, halt ou coisa parecida acontecendo antes do free.
  3. Instanciar duas vezes umobjeto em uma mesma variável, perdendo a referencia do primeiro.
  4. Ponteiros que apontam para estruturas alocadas dinamicamente que você esquece de dar um freemem.

A edição 72 da revista Clubedelphi fala sobre isso, e até fala sobre quando usar os owners self, application, formx ou nil.Nem sempre estamos dentro de uma form, nem sempre nossa classe é um descendente de TComponent,então passar self, application ou qualquer outra coisa é impossível,certo? Então temos de usar o nil.

Um outro motivo para se usar o nil é que cada vez que você usa como owner self, outro componente ou application (que também é um Tcomponent), uma refêrencia a este objeto será adicionada a um array de components do owner. Imagine que cada componente, inclusive application, tem um vetor de componentes. E cada vez que este componente vira owner de uma nova instancia de um outro componente qualquer (quando você instancia um componente com owner diferente de nil), um ponteiro para essa nova instancia (4 bytes, o mesmo tamanho de um integer) é adicionado a esse vetor. Quando esse owner é liberado da memória com free um contador vai percorrendo todo esse vetor dando free para destruir cada um desses outros componentes instanciados. Imagine o tempo e o custo disso, em memória e processador, para liberar da memória um cara que seja owner de 1000000 de objetos, por exemplo.

Se quiser faça o teste: crie uma classe pesada, tipo um form. Faça um loop de 1 a 1000000 instanciando uma copia desse form passando como parâmetro owner ou o application ou uma outra form qualquer. Não precisa dar show. Independente de você liberar da memória depois ou não, veja o tempo que demora. Você pode usar para isso o gettickcount.

Faça o mesmo teste, só que dessa vez passando nil. Bem mais rápido certo? Isso porque quando você passa nil como owner o componente não tem owner, então você economiza varia operações,como colocar uma referencia a esse objeto no vetor de componentes(sem owner ele não existe).Mas e para liberar da memória? Agora não vai liberar sozinho....Sempre que você criar um componente em runtime com owner nil faça da seguinte maneira:

 Try MinhaQuery := TQuery.Create(nil);
  //operações com MinhaQuery......
  finally
   MinhaQuery.Free;
  End;

Ou

Try
 Try
  MinhaQuery :=TQuery.Create(nil);
   //operações com MinhaQuery......
    Except
    //tratamento da exceção
  end;
  finally
   MinhaQuery.Free;
End;

Isso garante que o free sempre será executado e o objeto sempre liberado da memória.Agora, como detectar memory leaks que já existem a um tempão no sistema por falta de atenção e você não sabe exatamente onde?

Você pode usar o CNWizards(conhecido também como CNPack), que é um excelente conjunto de bibliotecas e add-ins para o delphi. Ou você pode usar o FastMM4.

  • http://www.cnpack.org/
  • http://sourceforge.net/projects/fastmm/

Usando o CNpack:Numa form qualquer coloque um Button e no onclick dele digite o código abaixo:

procedureTForm1.btCriaClick(Sender:TObject);
	var
	 obj: TStringList;
	 begin
	  obj := TStringList.Create;
	   obj.Add('vitor');
	    //tirando apenas essa linha podemos criar um tipo de memory leak muito comum, 
	    //que é instanciar em pontos diferentes do código dois objetos
	    //numa mesma variavel e esquecer de libera-los
	    //obj.Free;<br> obj := TStringList.Create;
	   obj.Add('rubio');
	   obj.Free;
	   //outro tipo comum de memory leak,criar objetos para uso imediato e esquecer de liberar
	    {
	      withTStringList.Create do
	       begin
	        //Free;
	       end;
	       withTButton.Create(nil) do
	       begin
	        Name := 'btZeh';
	        //Free;
	        end;
	    }
	end;

Repare que o código possui 3 maneiras diferentes de se causar um memory leak, mas duas estão comentadas, usaremos uma só. Fique a vontade para testar as outras. Se você instalou corretamente o CnPack, agora você tem alguns novos templates de projetos, e o search path do seu delphi aponta para alguns locais onde tem algumas novas units interessantes.Vá para a unit do seu projeto.dpr (project1.dpr) e adicione em primeiro lugar no uses a unit cnMemProf, assim:

programProject1;
	uses
	CnMemProf,
	Forms,
	Unit1 in 'Unit1.pas' {Form1};
{$R *.RES}

//depois do begin, antes do application.initialize configure o valor dessas 5 variáveis globais:
//PopupMsgDlg := True;
//ShowObjectInfo := True;
//UseObjectList := True;
//SaveToLogFile := True;
//ErrLogFile :='D:\vitor\exemplos\CnMemoryProfiler\log.log'.

Seu dpr ficará assim:

programProject1;
	uses
	CnMemProf,
	Forms,
	Unit1 in 'Unit1.pas' {Form1};

{$R *.RES}
begin
//configuração do CnMemProf
PopupMsgDlg := True;
ShowObjectInfo := True;
UseObjectList := True;
SaveToLogFile := True;
ErrLogFile :='D:\vitor\exemplos\CnMemoryProfiler\log.log';
Application.Initialize;
Application.CreateForm(TForm1,Form1);
 Application.Run;
 end.

Quanto mais informação de debug melhor, então vá a Project options e na aba compiler marque “use debug DCUs” (desmarcando o“optimization” e marcando o “stack Frames” você tem mais informações de debug, mas essas não vão ser usadas aqui)Execute a aplicação,clique no botão que cria os objetos sem destruir e feche a aplicação. Você receberá uma mensagem dizendo que ocorreram memory leaks. Veja o log:


24/11/2008 15:56:43
Application total run time: 0 hour(s) 0minute(s) 2
second(s)¡£
There are 77 allocated before replacememory manager.
HeapStatus.TotalAddrSpace: 1024 KB
HeapStatus.TotalUncommitted: 992 KB
HeapStatus.TotalCommitted: 32 KB
HeapStatus.TotalFree: 29 KB
HeapStatus.TotalAllocated: 1 KB
TotalAllocated div TotalAddrSpace: 0%
HeapStatus.FreeSmall: 0 KB
HeapStatus.FreeBig: 29 KB
HeapStatus.Unused: 0 KB
HeapStatus.Overhead: 0 KB
Objects count in memory: 3
1) 0000000000CA4A8C - 55($0037)Byte -
2) 0000000000CA4AC0 - 38($0026)Byte -
3) 0000000000CA38F0 - 23($0017)Byte –

Certo, quem não manja muito de hexadecimal e nem de assembly, como é meu caso, percebe que há um memory leak, mas não exatamente onde. Vamos ver se conseguimos uma informação mais detalhada.

Crie um outro projeto igual a esse,descompacte as units da biblioteca FastMM4 na pasta do projeto e edite o arquivo FastMM4Options.inc. Mude a linha {.$define FullDebugMode}para {$define FullDebugMode} e a linha {.$define ClearLogFileOnStartup} para {$define ClearLogFileOnStartup}(para limpar o log a cada nova execução).

Adicione a unit FastMM4 como a primeira unit no uses do seu project1.dpr e sete os valores das variáveis:

FullDebugModeScanMemoryPoolBeforeEveryOperation := True;
SuppressMessageBoxes:=False;

O seu dpr ficará assim:

programProject1;
	uses
	FastMM4,
	Forms,
	Unit1 in 'Unit1.pas' {Form1};
	{$R *.RES}
	begin

 //configuração do FastMM4

FullDebugModeScanMemoryPoolBeforeEveryOperation := True;
 SuppressMessageBoxes:=False;
  Application.Initialize;
  Application.CreateForm(TForm1,Form1);
  Application.Run;
end.

Execute a aplicação, crie o objeto sem destruir (clicando no botão), feche a aplicação e verá que aparecerá uma mensagem de memory leak um pouco mais detalhada. Agora veja o log, atenção para a área em negrito:


--------------------------------2008/11/2416:11:25--------------------------------
A memory block has been leaked. Thesize is: 20
This block was allocated by thread0x2C4, and the stack trace (return
addresses) at the time was:
402D38[system.pas][System][@GetMem][2439]
41B68A[classes.pas][Classes][TStringList.AddObject][4589]
41B60E[classes.pas][Classes][TStringList.Add][4576]
460058[Unit1.pas][Unit1][TForm1.btCriaClick][59]
4398BC[Controls.pas][Controls][TControl.Click][4705]
4308D4[StdCtrls.pas][StdCtrls][TButton.Click][3472]
430A3B[StdCtrls.pas][StdCtrls][TButton.CNCommand][3524]
43968E[Controls.pas][Controls][TControl.WndProc][464]