terça-feira, 31 de março de 2009

Detectando memory leaks, CnMemoryProfiler e FastMM4

    Vamos conversar sobre memory leaks.



    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 4 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 2 vezes um objeto em uma mesma variável,
    perdendo a referência do primeiro.

  4. Ponteiros que apontam para estruturas alocadas dinamicamente que
    você esquece de dar um freemem.


    As edições 72, 74 e 75 da revista Clube Delphi, que traziam artigos sobre interfaces e POO, falavam
sobre isso, e até falavam 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 referencia 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,
omesmo tamanho de um integer) é adicionado a esse vetor.
Quando esse owner é liberado da memória com free um
contadorvai 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
ounã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 damemó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 comMinhaQuery......
finally
    MinhaQuery.Free;
End;




Ou




Try
    Try
        MinhaQuery :=TQuery.Create(nil);
       //operações comMinhaQuery......
    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 memoryleaks que já existem a um
tempão no sistema por falta de atençãoe 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/ color="#0000ff">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 podemo scriar um tipo de memory leak muito comum, que é instanciar em pontos diferentes do código dois objetos 
    //numa mesma variavel eesquecer de libera-los
    //obj.Free;
    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 variaveis globais:
mmPopupMsgDlg := True;
mmShowObjectInfo := True;
mmUseObjectList := True;
mmSaveToLogFile := True;
mmErrLogFile :='D:\vitor\exemplos\CnMemoryProfiler\log.log'.





    Seu dpr ficará assim:


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

{$R *.RES}
begin

   //configuração doCnMemProf

    mmPopupMsgDlg := True;
    mmShowObjectInfo := True;
    mmUseObjectList := True;
    mmSaveToLogFile := True;
    mmErrLogFile :='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
novaexecuçã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 umpouco 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][4645]

43D20B[Controls.pas][Controls][TWinControl.WndProc][6342]

430723[StdCtrls.pas][StdCtrls][TButtonControl.WndProc][3414]

439399[Controls.pas][Controls][TControl.Perform][4552]

The block is currently used for anobject of class: Unknown

The allocation number is: 504

Current memory dump of 256 bytesstarting at pointer address 7FF8F570:

01 00 00 00 05 00 00 00 76 69 74 6F 7200 EE 00 19 7E 80 80 80 80 80 80
00 00 00 00 D1 F6 F8 7F

00 00 00 00 00 00 00 00 00 00 00 00 0000 00 00 96 01 00 00 38 2D 40 00
F8 E0 41 00 EF E3 41 00

28 E3 41 00 C7 0B 42 00 AA 6B 43 00 15B1 43 00 34 58 45 00 EA F1 41 00
3C C3 41 00 AD 95 41 00

C4 02 00 00 63 2D 40 00 8B AF 43 00 8746 45 00 F0 54 45 00 89 08 42 00
03 31 45 00 76 44 40 00

D7 6F 81 7C 00 00 00 00 00 00 00 00 0000 00 00 C4 02 00 00 14 00 00 00
00 00 00 00 D4 03 2A 01

84 6C 46 00 80 80 80 80 80 80 80 80 8080 80 80 80 80 80 80 2B FC D5 FE
00 00 00 00 B1 F3 F8 7F

00 00 00 00 00 00 00 00 00 00 00 00 0000 00 00 FB 01 00 00 38 2D 40 00
8A B6 41 00 0E B6 41 00

74 00 46 00 BC 98 43 00 D4 08 43 00 3B0A 43 00 8E 96 43 00 0B D2 43 00
23 07 43 00 99 93 43 00

. . . . . . . . v i t o r . î . . ~ € € € €
€ € . . . . Ñ ö ø 

. . . . . . . . . . . . . . . . – . . . 8 - @ . ø à
A . ï ã A .

( ã A . Ç . B . ª k C . . ± C . 4 X E .
ê ñ A . < Ã A . ­ • A .

Ä . . . c - @ . ‹ ¯ C . ‡ F E . ð T E .
‰ . B . . 1 E . v D @ .

× o � | . . . . . . . . . . . . Ä . . . . . . . . . . .
Ô . * .

„ l F . € € € € € € € €
€ € € € € € € € + ü
Õ þ . . . . ± ó ø 

. . . . . . . . . . . . . . . . û . . . 8 - @ . Š ¶ A
. . ¶ A .

t . F . ¼ ˜ C . Ô . C . ; . C . Ž – C . .
Ò C . # . C . ™ “ C .

--------------------------------2008/11/2416:11:25--------------------------------

A memory block has been leaked. Thesize is: 36

This block was allocated by thread0x2C4, and the stack trace (return
addresses) at the time was:

402DBD[system.pas][System][@ReallocMem][2550]

41BA7D[classes.pas][Classes][TStringList.Grow][4705]

41BB90[classes.pas][Classes][TStringList.InsertItem][4730]

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][4645]

43D20B[Controls.pas][Controls][TWinControl.WndProc][6342]

The block is currently used for anobject of class: Unknown

The allocation number is: 503

Current memory dump of 256 bytesstarting at pointer address 7FF95880:

78 F5 F8 7F 00 00 00 00 80 80 80 80 8080 80 80 80 80 80 80 80 80 80 80
80 80 80 80 80 80 80 80

E4 8D 55 FC 80 80 80 80 00 00 00 00 6159 F9 7F 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

B1 01 00 00 38 2D 40 00 C1 66 43 00 B8AD 43 00 C0 05 43 00 4C 08 43 00
04 DF 41 00 39 E1 41 00

EF E3 41 00 28 E3 41 00 C7 0B 42 00 AA6B 43 00 C4 02 00 00 63 2D 40 00
DF 37 40 00 D2 AF 43 00

8B AF 43 00 87 46 45 00 F0 54 45 00 8908 42 00 03 31 45 00 76 44 40 00
D7 6F 81 7C 00 00 00 00

C4 02 00 00 24 00 00 00 F8 26 42 00 7623 F1 01 84 6C 46 00 80 80 80 80
80 80 80 80 80 80 80 80

80 80 80 80 80 80 80 80 80 80 80 80 8080 80 80 80 80 80 80 89 DC 0E FE
00 00 00 00 11 5A F9 7F

00 00 00 00 00 00 00 00 00 00 00 00 0000 00 00 B2 01 00 00 38 2D 40 00
F5 66 43 00 B8 AD 43 00

x õ ø  . . . . € € € € € €
€ € € € € € € € € €
€ € € € € € € €

ä � U ü € € € € . . . . a Y ù face="Batang, 바탕, serif">. . . . . . . . . . . . . . . .

± . . . 8 - @ . Á f C . ¸ ­ C . À . C .
L . C . . ß A . 9 á A .

ï ã A . ( ã A . Ç . B . ª k C . Ä .
. . c - @ . ß 7 @ . Ò ¯ C .

‹ ¯ C . ‡ F E . ð T E . ‰ . B . . 1 E . v
D @ . × o � | . . . .

Ä . . . $ . . . ø & B . v # ñ . „ l F .
€ € € € € € € € € €
€ €

€ € € € € € € € € €
€ € € € € € € € € €
‰ Ü . þ . . . . . Z ù 

. . . . . . . . . . . . . . . . ² . . . 8 - @ . õ f C .
¸ ­ C .

--------------------------------2008/11/2416:11:25--------------------------------

A memory block has been leaked. Thesize is: 52

This block was allocated by thread0x2C4, and the stack trace (return
addresses) at the time was:

402D38[system.pas][System][@GetMem][2439]

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][4645]

43D20B[Controls.pas][Controls][TWinControl.WndProc][6342]

430723[StdCtrls.pas][StdCtrls][TButtonControl.WndProc][3414]

439399[Controls.pas][Controls][TControl.Perform][4552]

43D3CD[Controls.pas][Controls][DoControlMsg][6388]

43DBAA[Controls.pas][Controls][TWinControl.WMCommand][6574]

458F35[Forms.pas][Forms][TCustomForm.WMCommand][4116]

The block is currently used for anobject of class: TStringList

The allocation number is: 502

Current memory dump of 256 bytesstarting at pointer address 7FF99870:

FC 76 41 00 00 00 00 00 00 00 00 00 0000 00 00 80 58 F9 7F 01 00 00 00
04 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 0000 00 00 EB 89 4A 7A 80 80 80 80
00 00 00 00 01 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 0000 00 00 F9 01 00 00 38 2D 40 00
BC 98 43 00 D4 08 43 00

3B 0A 43 00 8E 96 43 00 0B D2 43 00 2307 43 00 99 93 43 00 CD D3 43 00
AA DB 43 00 35 8F 45 00

C4 02 00 00 63 2D 40 00 DF 37 40 00 BC98 43 00 D4 08 43 00 3B 0A 43 00
8E 96 43 00 0B D2 43 00

23 07 43 00 99 93 43 00 CD D3 43 00 AADB 43 00 C4 02 00 00 30 00 00 00
FC 76 41 00 3A F6 FF 85

84 6C 46 00 80 80 80 80 80 80 80 80 8080 80 80 80 80 80 80 80 80 80 80
80 80 80 80 80 80 80 80

80 80 80 80 80 80 80 80 80 80 80 80 8080 80 80 C5 09 00 7A 80 80 80 80
00 00 00 00 00 00 00 00

ü v A . . . . . . . . . . . . . € X ù  . . . . . . .
. . . . .

. . . . . . . . . . . . . . . . ë ‰ J z € €
€ € . . . . . . . .

. . . . . . . . . . . . . . . . ù . . . 8 - @ . ¼ ˜
C . Ô . C .

; . C . Ž – C . . Ò C . # . C . ™ “ C .
Í Ó C . ª Û C . 5 � E .

Ä . . . c - @ . ß 7 @ . ¼ ˜ C . Ô . C . ;
. C . Ž – C . . Ò C .

# . C . ™ “ C . Í Ó C . ª Û C .
Ä . . . 0 . . . ü v A . : ö ÿ …

„ l F . € € € € € € € €
€ € € € € € € € € €
€ € € € € € € € € €

€ € € € € € € € € €
€ € € € € € Å . . z € €
€ € . . . . . . . .

--------------------------------2008/11/2416:11:25--------------------------------

This application has leaked memory. Thesmall block leaks are (excluding
expected leaks registered bypointer):

13 - 20 bytes: AnsiString x 1

21 - 36 bytes: Unknown x 1

37 - 52 bytes: TStringList x 1

Note: Memory leak detail is logged to atext file in the same folder as
this application. To disable thismemory leak check, undefine
"EnableMemoryLeakReporting".



    No caso, no meu projeto o memory leak ficou na unit1,
form1 linha 59. Se eu olhar a linha 59 da unit1:obj.Add('vitor');

    Ou seja, é uma linha muito próxima da
onde está acontecendo o memory leak: é um pouco depois da
criação do primeiro objeto e um pouco antes da
criação do segundo, onde eu crio por cima da mesma
variável perdendo a referencia ao meu primeiro objeto, que fica
órfão na memória.

    Com um pouco de atenção podemos
resolver quase todos os problemas olhando nesse log, a menos que sejam
problemas de bibliotecas de terceiros as quais desconhecemos.

    Espero que tenha ajudado todos adetonar seus memory
leaks. Quem tiver uma sugestão ou souberde algum recurso ou
melhor uso do CnMemory Profiler ou do FastMM4 porfavor poste aqui para
um intercâmbio de conhecimento.


Você pode fazer o download desse exemplo, já com o FastMM4.90 nesse link.

Nenhum comentário:

Postar um comentário

Postagens populares

Marcadores

delphi (60) C# (31) poo (21) Lazarus (19) Site aos Pedaços (15) sql (13) Reflexões (10) .Net (9) Humor (9) javascript (9) ASp.Net (8) api (8) Básico (6) Programação (6) ms sql server (5) Web (4) banco de dados (4) HTML (3) PHP (3) Python (3) design patterns (3) jQuery (3) livros (3) metaprogramação (3) Ajax (2) Debug (2) Dicas Básicas Windows (2) Pascal (2) games (2) linguagem (2) música (2) singleton (2) tecnologia (2) Anime (1) Api do Windows (1) Assembly (1) Eventos (1) Experts (1) GNU (1) Inglês (1) JSON (1) SO (1) datas (1) developers (1) dicas (1) easter egg (1) firebird (1) interfaces (1) introspecção (1) memo (1) oracle (1) reflexão (1)