Net-Base Revista

23.08.2026

Caçar vazamentos de memória: usar o FastMM FullDebugMode de forma direcionada e ler Stacktraces corretamente

FastMM FullDebugMode é, em projetos Delphi, uma das ferramentas mais eficazes na deteção de vazamentos de memória – mas apenas se for ativado de forma dirigida, os relatórios forem interpretados corretamente e erros de interpretação comuns forem evitados. Este artigo prático mostra um fluxo de trabalho claro desde...

23.08.2026

Do tema da revista à prática do projeto

Páginas de serviços e técnicas correspondentes ao artigo

Quando uma Delphi-aplicação em produção começa a „inchar“ lentamente, encerra-se esporadicamente com violações de acesso ou torna-se instável após dias de execução, frequentemente não há um bug isolado, mas um padrão: memória é solicitada e não é liberada corretamente — ou é liberada cedo demais e depois ainda é usada. É exatamente aí que o FastMM FullDebugMode vale ouro. Não como estado permanente, mas como ferramenta de diagnóstico direcionada que transforma um „algo no heap está corrompido“ em uma causa rastreável.

O problema: o FullDebugMode produz muito output, custa performance e rapidamente leva a interpretações errôneas. Um relatório de vazamento de memória não mostra automaticamente o local onde „o erro“ está. E um stacktrace só é tão bom quanto a resolução de símbolos (arquivo MAP, informações de debug, inlining). Neste artigo eu percorro o caso limite típico, explico a abordagem correta e as armadilhas — de forma que você, ao final, não só encontre vazamentos, mas os corrija de forma duradoura.

Quando o FastMM FullDebugMode é realmente útil

FastMM está, em versões modernas do Delphi, frequentemente já como gerenciador de memória padrão ou é incorporado em muitos projetos. O FullDebugMode, porém, é uma configuração especial: ele marca blocos de memória com padrões de verificação adicionais, coleta stacktraces de alocação e verifica de modo mais agressivo a corrupção do heap (isto é, dados de gerenciamento no heap corrompidos, por exemplo por estouro de buffer).

Eu ativo o FullDebugMode de forma direcionada quando se aplica uma destas situações:

  • Vazamento reproduzível: o consumo de memória aumenta no teste por operação (por exemplo por requisição, por importação, por ação da UI).
  • AVs esporádicas: especialmente aquelas que ocorrem „às vezes aqui, às vezes ali“ na mesma região (clássico: uso após liberação).
  • Corrupção do heap: mensagens como „Invalid pointer operation“, „Access violation in ntdll“ ou crashes ao encerrar/finalizar.
  • Busca de regressão: instabilidade nova após refatoração, atualização de biblioteca ou troca de compilador.

Não faz sentido usar o FullDebugMode como um „vamos ativar em todos os builds“. O overhead é alto, o timing muda e justamente condições de corrida podem desaparecer ou se deslocar por causa disso. Para operação contínua é mais apropriado um monitoramento enxuto (por exemplo working set do processo, Private Bytes, contadores por operação) — o FullDebugMode é o bisturi, não o monitor de pulso.

Princípio básico: relatório de vazamento é sintoma, stacktrace é pista

Um relatório de vazamento mostra primeiro: estes blocos ainda estão alocados ao final do programa. Isso só é automaticamente um problema se esses blocos deveriam ter sido liberados. Existem „vazamentos“ legítimos: singletons globais, caches, handles do SO com lifetime do processo ou bibliotecas de terceiros que intencionalmente não finalizam. Esses casos você deve conhecer, mas não corrigir cegamente.

O stacktrace no relatório mostra o ponto em que o bloco foi alocado. Isso frequentemente não é o local onde você „esqueceu o Free“. Realidade comum em sistemas legados:

  • Alocação na camada de UI ou de serviço; a liberação deveria ocorrer em uma camada mais profunda (ownership não clara).
  • Alocação em uma factory; a ownership é passada para o caller — mas o caller pensa que seria „owned“.
  • Objetos são mantidos em coleções (listas, dicionários), mas o modelo de ownership não é consistente.
  • Um caminho de exceção pula o cleanup porque falta try/finally ou ele começa tarde demais.

O fluxo limpo é, portanto: reproduzirisolarresolver o Stacktraceencontrar erro de Ownershipfix com teste de regressão. FastMM fornece as pistas, mas você precisa traduzi-las para arquitetura e ciclos de vida.

Ativar corretamente o FastMM FullDebugMode (sem ignorar efeitos colaterais)

Schematische Grafik mit Heap-Blöcken und Prüfrändern, die in einen Leak-Report überführt werden
Resumo: o FullDebugMode trabalha com margens de verificação adicionais e saída de relatório.

O FullDebugMode é ativado na prática através das opções do FastMM e de uma configuração apropriada do FastMM. O que importa menos é “qual é exatamente o nome do arquivo include”, e mais o que a configuração provoca e sob quais condições de build você a utiliza.

Condições recomendadas para o debug-build

  • DCUs de depuração e informações de debug: Stacktraces só são úteis se puderem ser resolvidos para unidade/linha/endereço reais. Garanta que sejam geradas informações de depuração e que um arquivo MAP esteja disponível.
  • Escolher otimização conscientemente: Para a legibilidade do stacktrace normalmente um build não otimizado é melhor. Inlining e otimizações agressivas podem dificultar a interpretação dos frames de pilha.
  • Mesmas condições de execução: Use, sempre que possível, os mesmos dados, a mesma configuração, os mesmos privilégios. Muitos leaks dependem dos dados (por exemplo, formatos raros, caminhos especiais).
  • Separar 64-bit e 32-bit: Comportamento de memória, alinhamento e bibliotecas de terceiros diferem. Depure na plataforma alvo em que o problema acontece.

Um ponto que administradores e líderes técnicos frequentemente subestimam: o FullDebugMode também pode alterar o timing. Se houver threading em jogo, condições de corrida podem manifestar-se de maneira diferente. Por isso é sensato ter paralelamente uma execução sem FullDebugMode que apenas confirme a reprodução. O FullDebugMode é então o passo para o diagnóstico.

Vorsicht bei „ReportMemoryLeaksOnShutdown“

Delphi pode relatar leaks no término do programa via ReportMemoryLeaksOnShutdown. Isto é prático, mas em aplicações complexas (services, plug-in hosts, tempos de execução longos) pode enganar: durante o shutdown executam-se seções de finalização, threads param, caches são limpos. Um leak que é crítico durante a execução pode desaparecer até o final — ou, inversamente, um leak aparente só surge no shutdown porque ainda há trabalho em background em andamento.

Para caça a leaks prática é, portanto, mais importante: medir leak por operação (por exemplo, após 100 requisições), e não apenas ao encerrar. FastMM pode ajudar nisso, mas o cenário de teste precisa representar isso.

O caso limite típico: relatório de leak mostra „irgendein Objekt“, mas a causa é Ownership

Um clássico em aplicações corporativas: um processo de importação cria por registro objetos auxiliares (por exemplo, StringLists, JSON-Parser, listas temporárias). No happy path eles são liberados corretamente. Em casos raros (pulo por validação, exceção, saída prematura) um objeto fica preso. Após 10.000 registros isso fica visível.

FastMM FullDebugMode ajuda aqui, porque mostra o local da alocação. Mas a „correção“ não é „free no ponto da alocação“. A correção é um padrão de ownership robusto:

  • Quem cria um objeto não é automaticamente o owner.
  • A ownership deve estar clara no contrato da API (parâmetros/retorno, documentação, convenções de nomenclatura).
  • Coleções devem ser inequívocas: owning vs. non-owning. Formas mistas se vingam.
  • Caminhos de exceção precisam de blocos try/finally antecipados.

Se no stacktrace você vê apenas „TStringList.Create“, a informação não é inútil — mas ela só diz: algo está sendo criado aqui. A pergunta é: onde isso deveria terminar? E aí o pensamento arquitetural ajuda mais do que acrobacias do depurador.

Stacktraces corretamente lidos: o que você realmente pode deduzir

Detailaufnahme einer Debugging-Analyse mit unscharfem Debugger und handnotierter Call-Chain
No stacktrace o que conta é a call-chain – não a linha isolada.

Um stacktrace do FastMM é, em geral, uma lista de endereços de retorno que — com símbolos de debug — são mapeados para units, procedimentos e idealmente números de linha. Ao ler isso, três coisas são decisivas:

  • Top-of-Stack nem sempre é o erro: os frames superiores costumam pertencer ao memory manager/RTL. Fica interessante onde seu código começa.
  • Call-Chain em vez de linha isolada: a linha é apenas um ponto. A cadeia mostra qual caminho levou à alocação.
  • Vários blocos idênticos: se o FastMM reporta vários leaks do mesmo tamanho, frequentemente é um caminho recorrente. Isso é bom: você tem reprodutibilidade.

Quando faltam números de linha: MAP-Datei, Packages, Release-DCUs

Muitas equipes tropeçam aqui: FullDebugMode está ativo, o relatório de leaks aparece, mas em vez de unidade/linha há apenas endereços ou símbolos crípticos. Causas típicas:

  • Arquivo MAP ausente ou informações de debug não geradas.
  • Você está contra Release-DCUs ou DLLs de terceiros sem símbolos.
  • A aplicação usa runtime packages: então partes do código residem em BPLs, e a resolução de símbolos precisa estar alinhada.
  • Otimização/inlining tornou o stacktrace menos legível.

Na prática isso significa: para a caça a leaks você precisa de um build que seja intencionalmente „diagnosticável“. Esse é um objetivo diferente de „o mais rápido possível“. Leads técnicos devem tratar isso como um perfil de build próprio, para que nem todo membro da equipe altere opções de projeto ad hoc.

Avaliar frames: „Interessant“ está muitas vezes uma linha acima

Um exemplo da vida real (sem código de cliente concreto): o stacktrace mostra, como primeiro frame no seu código, uma rotina „LoadConfig“. Você vê ali uma criação de objeto. Você adiciona um Free, o vazamento some — e de repente estoura em outro ponto com um Double Free. Por quê? Porque „LoadConfig“ coloca o objeto em um cache, e outro caminho de código já é o proprietário e faz a limpeza mais tarde.

A leitura correta teria sido: o stacktrace te mostra, onde o bloco é criado. A correção está frequentemente na definição: Quem possui o objeto após o return? Se você não responder essa pergunta de forma clara, você apenas troca o sintoma do erro (Vazamento → AV).

Heap-Korruption vs. Leak: Warum FullDebugMode oft den echten Übeltäter findet

Grafik, die einen Buffer-Overrun zeigt, der in benachbarten Speicherbereich überläuft
Corrupção de heap costuma manifestar-se com atraso — o FullDebugMode torna-a visível mais cedo.

Muitos “leaks” são, na verdade, problemas secundários: um buffer-overrun sobrescreve metadados do heap, o gerenciador de memória não consegue liberar corretamente mais tarde, e no fim você vê vazamentos aparentemente aleatórios ou operações de ponteiro inválidas. O FullDebugMode é eficaz aqui porque trabalha com padrões de verificação e executa validações adicionais em Free/Reuse.

É importante distinguir:

  • Vazamento de memória: o bloco foi alocado e nunca liberado. A estabilidade degrada ao longo do tempo; um crash não é obrigatório.
  • Uso após liberação: o bloco é liberado, mas é usado mais tarde. Leva a AVs esporádicos, difíceis de reproduzir.
  • Liberação dupla: o bloco é liberado duas vezes. Pode falhar imediatamente ou só mais tarde (quando o bloco foi reutilizado).
  • Corrupção de heap: alguém escreve além dos limites de um bloco. Os sintomas costumam aparecer com atraso.

O FullDebugMode é especialmente valioso quando você vê sintomas com atraso. A validação adicional torna os erros visíveis mais cedo — frequentemente exatamente no ponto em que ocorre o acesso incorreto, e não apenas minutos depois em um Free qualquer.

Procedimento em projetos: caça reprodutível a vazamentos em vez de “debugging na névoa”

Se você quer caçar vazamentos de memória, precisa de um procedimento que seja repetível e possa ser compartilhado na equipe. Eu prefiro trabalhar com um quadro de diagnóstico definido:

1) Reprodução em um cenário determinístico

Defina uma sequência de teste que demonstre o vazamento de forma confiável: „Inicie o serviço, processe 500 mensagens, pare o serviço“ ou „Abra a tela X, execute a ação Y 200 vezes“. É importante documentar a sequência com parâmetros (base de dados, cliente, feature-flags), para que outros possam reproduzi-la.

2) Minimizar: tornar o vazamento visível por etapa

Se a sequência demora 20 minutos, divida-a. O objetivo é: você quer comparar o mais rápido possível entre “antes” e “depois”. Em aplicações grandes, isso costuma ser o que consome mais tempo, não o conserto em si.

3) FullDebugMode einschalten und Report interpretieren

Só então entra em jogo o FastMM FullDebugMode. Reúna os relatórios, agrupe por tamanho do bloco/callstack e procure repetições. Um único bloco remanescente pode ser um cache legítimo. 10.000 blocos idênticos são quase sempre um vazamento real.

4) Ownership-Klärung und Fix in der passenden Schicht

Corrija leaks onde a Ownership é definida: Factory, contrato da API, Collection-Wrapper. “Colocar rapidamente um Free” imediatamente ao lado do Create costuma ser o lugar errado, se o objeto for repassado.

5) Regression: gleiche Sequenz, gleicher Build, gleicher Report

A correção só é válida quando a sequência volta a rodar e não surgem leaks nem novos erros de memória. Especialmente em casos de Use-after-free, um “Leak weg” não é prova, mas apenas um novo sintoma.

Typische Fallstricke in Delphi-Code, die FastMM sichtbar macht

Collections und Ownership (Listen, Dictionaries, Interfaces)

Muitos vazamentos não vêm de algoritmos complicados, mas de estruturas de dados do dia a dia. Dois padrões clássicos de erro:

  • Uma lista contém objetos, mas ninguém sabe quem os libera. Solução: usar uma lista „owning“ ou esvaziá-la consistentemente no finally.
  • Um Dictionary mantém objetos como Values; ao Remove o Value não é liberado ou é esquecido no Clear.

Além disso, Interfaces são complicadas: contagem de referência (semelhante a ARC) é confortável, mas o modo misto com object-ownership pode gerar leaks em referências cíclicas ou eventos. O FullDebugMode frequentemente mostra o caminho de alocação, mas a causa é um ciclo de referência (A mantém B via Interface, B mantém A via Callback).

Exceptions und frühe Exits

Em sistemas de software empresarial maduros, Exceptions frequentemente fazem parte do fluxo normal (p.ex. validação, cancelamento, retry). O problema raramente é a Exception em si, mas o caminho ao redor: um objeto é criado antes do try/finally, então ocorre uma Exception e o cleanup é pulado. O FullDebugMode fornece a stacktrace da alocação — e você deve verificar se existe um caminho de liberação que seja garantidamente executado.

Threads und Lebenszeit: „Freigeben im falschen Thread“

Com VCL/FMX e serviços com worker-threads surge outro caso de borda: um objeto é criado em uma thread, mas liberado no UI-Thread (ou vice-versa), porque se passa “só rapidinho” algo via Queue/Synchronize. Isso pode funcionar, mas também pode causar Use-after-free se o Producer continuar trabalhando enquanto o Consumer já libera.

O FastMM FullDebugMode pode ajudar aqui, porque detecta erros temporais mais cedo. A correção propriamente dita é, no entanto, um modelo de tempo de vida limpo: propriedade clara, passagem apenas por dados immutable ou pontos de Ownership-Transfer bem definidos.

Wie du Reports nutzbar machst: Filtern, vergleichen, dokumentieren

Em equipes vale a pena não apenas olhar os Leak-Reports, mas tratá-los como artefatos. Três medidas pragmáticas que se mostraram eficazes:

  • Relatório de baseline: um “estado conhecido” (p.ex. a versão atual do produto) é executado uma vez com FullDebugMode e arquivado como referência. Assim você identifica novos leaks imediatamente.
  • Comparação por Use-Case: Para fluxos críticos (Import, Export, API-Request, operações em massa na UI) você define uma sequência curta que seja repetível regularmente.
  • Vazamentos “legítimos” documentados: se um cache conscientemente não é finalizado, documente isso. Caso contrário, em seis meses alguém volta a caçar as mesmas entradas.

Isso não é burocracia, mas uma economia de tempo: a caça a vazamentos de memória rapidamente se torna um ciclo sem fim, porque os mesmos padrões reaparecem a cada Sprint.

Quando o esforço compensa – e quando você deve proceder de outra forma

FastMM FullDebugMode é uma ferramenta de diagnóstico com custos. O esforço compensa especialmente quando:

  • A aplicação fica em execução por longos períodos (Service, Terminalserver-Client, Schichtsystem, 24/7-Prozesse).
  • Você processa fluxos de dados reais de clientes e não cobre todos os caminhos em testes.
  • A estabilidade é mais importante do que a velocidade de entrega de funcionalidades no curto prazo (típico em soluções de software próximas ao processo).

Se, por outro lado, você tem apenas um pequeno desktop‑helper que termina após 30 segundos, a caça a vazamentos costuma ser secundária. Da mesma forma: se você tem um problema pontual de pico de memória (p. ex. uma exportação grande), frequentemente não é um vazamento, mas uma questão de estratégia de streaming e da carga de pico no Heap.

Conclusão prática: FullDebugMode não é um interruptor, mas um processo

FastMM FullDebugMode introduz estrutura na investigação de falhas de memória: torna alocações visíveis, detecta corrupção do Heap mais cedo e fornece stacktraces com os quais você pode corrigir a causa em vez do sintoma. O fator decisivo, porém, não é a ferramenta, e sim o processo: cenários reproduzíveis, builds com capacidade de diagnóstico, acordos claros de responsabilidade e regressão contra uma baseline.

Se você está preso a um vazamento persistente ou a um erro esporádico de Heap e quer estabilizar o tema de forma sustentável em um sistema maior Delphi, vale a pena um setup de diagnóstico curto e limpo com sequência clara e relatórios analisáveis. Se precisar de suporte em análise, perfis de build ou refatoração de arquitetura: contato com Net-Base Software GmbH.

Para este tema também são importantes Delphi Encontrar vazamentos de memória e Ler Fastmm Leak Report. O artigo ordena esses aspectos de forma compreensível e mostra no que prestar atenção no dia a dia.

Discutir projeto ou iniciativa de modernização com Net-Base.

Próximo passo

Quando o tema se tornar um projeto real, arquitetura, ambiente existente e operação devem ser considerados em conjunto desde o início.

Não apenas apoiamos questões pontuais, mas também quando fragmentos de código-fonte, temas legados ou ideias de portais precisam evoluir para um projeto empresarial robusto.

  • Estado atual, estado-alvo e riscos técnicos são avaliados em conjunto.
  • REST, o acesso a dados, os portais e o Rollout não são adiados para fases posteriores.
  • Você identifica cedo qual caminho é viável econômica e operacionalmente.

Partilhar publicação

Compartilhar esta publicação diretamente

LinkedIn, X, XING, Facebook, WhatsApp e E‑Mail estão disponíveis imediatamente. Para o Instagram, preparamos diretamente o link e o texto curto.

E-mail

O Instagram abre numa nova aba. O link e o texto curto são copiados previamente para a área de transferência.