Skip to content

Tag-icone-mini.png Programação, Delphi, OutOfMemory, MemoryLeak

Conceito ​

Nesta página são disponibilizados alguns exemplos de vazamento de memória encontrados no projeto TotaliMiddlewareService.

ACBrNew ​

Recentemente alguns clientes relataram um aumento rápido no consumo de memória do Middleware.
Verificando o Gerenciador de Tarefas, o Middleware aumentava de tamanho a cada minuto.
Neste caso o objeto que causou o vazamento de memória foi o objeto TACBrNFSeX.
Função TTotaliACBrHelper.ACBrNFSeXNew onde ocorria vazamento de memória

Podemos ver na imagem que há chamadas para outras funções após a instância da classe TACBrNFSeX.
O erro que ocorria neste caso era na função Self.ConfiguracaoTACBrNFSeXCfgEmpresa(Result, pCfgEmpresa) que lançava uma exceção.
Como no TTotaliACBrHelper.ACBrNFSeXNew não havia tratamento para exceções, a instancia da classe não era liberada da memória após ocorrer o erro.
Para corrigir colocamos todo o bloco de código após a instância da classe dentro de um Try..Except
Função TTotaliACBrHelper.ACBrNFSeXNew onde ocorria vazamento de memória

Desta forma garantimos que caso ocorra alguma exceção, o objeto instanciado é liberado da memória antes de propagar a exceção adiante.

LoadGuarda ​

Durante os testes encontramos vários leaks com o objeto TGuarda.
Neste caso o objeto, que é declarado localmente, recebe a instancia do LoadGuarda:
Exemplo de LoadGuarda.

Verificando no final da função, não é feita a liberação do objeto da memória.
Sem FreeAndNil no oGuarda

Para corrigir basta liberar ao final da função. Este é o exemplo mais simples e fácil de encontrar.
Com FreeAndNil no oGuarda

Referência Perdida ​

Este exemplo na rotina PersisteParceiro, mostra um memory leak, que ocorreu por referência perdida (ou sobrescrição de objeto)
Exemplo na função PersisteParceiro

A classe TResultServicoPersisteParceiro tem uma property que também é um objeto. No create da classe é feito o também o create da property.
Classe lista de TEndereco
Create da TResultServicoPersisteParceiro

Na função fromJSON é feito o create da classe TResultServicoPersisteParceiro
Logo após podemos ver que a property Enderecos recebe uma nova instância vinda da função EnderecoFromJSON
FromJSON onde é criada a classe TResultServicoPersisteParceiro

Este create sobreescreveu o FEnderecos criado no Create da classe TResultServicoPersisteParceiro
EnderecoFromJSON onde sobreescreveu a lista criada anteriormente

Para corrigir podemos usar o Setter da própria property
Property com SetEnderecos

Nele validamos se o objeto já está criado e liberamos da memória antes de setar um novo valor.
SetEnderecos Liberando a instancia antiga

Também devemos ter certeza que o objeto está sendo liberado no destroy da classe.
Destroy da classe TResultServicoPersisteParceiro

Funções que retornam objetos ​

Encontrei vários casos onde o objeto não era destruído pois era o result de uma função.
Este result não era alocado em nenhum objeto e a instância era vazada imediatamente após executar a função.

Exemplo na TTUDocEmitidoDAO:
Função CarregaDocumentoEmitido

Escopo da função ConsultaInterna

Esta função serve para retornar apenas um documento, porém a função ConsultaInterna retorna uma lista de documentos (TDocEmitido).
Para buscar o primeiro item da lista era feito um loop passando o índice diretamente para o Result, e após a primeira iteração o loop é quebrado (break).
Porém a lista não é liberada em lugar nenhum.

Esta implementação apresenta alguns problemas:

  • Não é validado se a lista contém documentos.
  • Não é desvinculado o objeto da lista. Mesmo se liberarmos a lista da memória, o objeto Result ficará inválido.
  • Ocorre vazamento de memória, pois a lista não foi liberada

Como resolver neste caso.
Criamos uma variável local para a lista que retorna da função:
Declarado lista no escopo local

Dentro de um bloco try..finally, validamos o count lista, para evitar erros de "Invalid Pointer Operation".
Em seguida, extraímos o objeto, desvinculando-o da lista. Após isso podemos passá-lo para Result.
Após extraírmos podemos liberar a lista normalmente, não vai mais liberar o objeto.
Validações e Extract para desvincular objeto da lista

Lembrar de destruir onde recebe o result da função CarregaDocumentoEmitido também!

Classes com erro de declaração ​

Um outro exemplo que encontramos foi na classe TVolume da unit TTUFrete
Nesta classe temos um objeto TEspecie que estava gerando leak.
Olhando o Destroy e o SetEspecie, tudo aparenta normal, porém o leak continuava.
Classe TVolume e objeto TEspecie Destroy da classe TVolume

O erro estava na declaração do método Destroy da classe Tvolume.
Estava declarado como OVERLOAD.
Overload no destroy da classe

Ao chamar o Free do TVolume não era executado o destroy.

Para corrigir, basta declarar o destroy corretamente como OVERRIDE.
Isso serve para sobreescrever a função da classe “pai” com a nova implementação.
Ainda irá chamar o destroy da classe pai com INHERITED
Destroy deve ter override

JSONObject e JSONArray ​

Um exemplo que encontrei no TTUCommerceAPI.
Loop onde não era onde ocorria vazamento de memória

Neste loop a ideia era popular o oJSONArray com o JSON convertido do objeto TChaveDocEmitido.
E na maioria das vezes o JSONArray se encarrega de destruir os objetos TJSONObject. Porém neste caso é adicionado somente o Texto em formato JSON.
O create da classe TChaveDocEmitido é feito no TChaveDocEmitido.fromDocEmitido, e é adicionado o array apenas o result da função TChaveDocEmitido.ToJSON.
A referência do objeto que executou o ToJSON não é passada para ninguém e perdida.

Como corrigir neste caso:
Criar o objeto separadamente, passando sua referência para uma variável.
Executar o que for preciso e liberar em seguida:
Loop onde não era onde ocorria vazamento de memória

Páginas Relacionadas:

Identificando Vazamentos de Memória com madExcept