Net-Base Revista

23.08.2026

Speicherlecks jagen: FastMM FullDebugMode gezielt nutzen und Stacktraces korrekt lesen

FastMM FullDebugMode ist in Delphi-Projekten eines der schärfsten Werkzeuge gegen Speicherlecks – aber nur, wenn man ihn gezielt einschaltet, die Reports richtig interpretiert und typische Fehlannahmen vermeidet. Dieser Praxisbeitrag zeigt einen sauberen Ablauf vom...

23.08.2026

Del tema de la revista a la pràctica del projecte

Pàgines de serveis i tècniques pertinents per a l'article

Wenn eine Delphi-Anwendung im Betrieb langsam „aufbläht“, sporadisch mit Access Violations aussteigt oder nach Tagen Laufzeit plötzlich instabil wird, steckt dahinter oft kein einzelner Bug, sondern ein Muster: Speicher wird angefordert, aber nicht sauber freigegeben – oder er wird zu früh freigegeben und später noch benutzt. Genau hier ist FastMM FullDebugMode Gold wert. Nicht als Dauerzustand, sondern als gezieltes Diagnosewerkzeug, das aus „irgendwo im Heap ist was kaputt“ wieder eine nachvollziehbare Ursache macht.

Der Haken: FullDebugMode produziert viel Output, kostet Performance und führt schnell zu Fehlinterpretationen. Ein Leak-Report zeigt nicht automatisch die Stelle, an der „der Fehler“ ist. Und ein Stacktrace ist nur so gut wie die Symbolauflösung (MAP-Datei, Debug-Infos, inlining). In diesem Beitrag gehe ich den typischen Randfall durch, erkläre den sauberen Ansatz und die Fallstricke – so, dass du am Ende nicht nur Leaks findest, sondern sie nachhaltig abstellst.

Wann FastMM FullDebugMode wirklich sinnvoll ist

FastMM ist in modernen Delphi-Versionen häufig bereits der Default-Memory-Manager oder wird in vielen Projekten ohnehin eingebunden. Der FullDebugMode ist aber eine besondere Konfiguration: Er versieht Speicherblöcke mit zusätzlichen Prüfmustern, sammelt Allokations-Stacktraces und prüft aggressiver auf Heap-Korruption (also beschädigte Verwaltungsdaten im Heap, z. B. durch Buffer-Overruns).

Ich setze FullDebugMode gezielt ein, wenn eines dieser Bilder vorliegt:

  • Reproduzierbares Leak: Speicherverbrauch steigt im Testlauf pro Vorgang (z. B. pro Request, pro Import, pro UI-Aktion).
  • Sporadische AVs: Besonders solche, die „mal hier, mal da“ im gleichen Bereich passieren (klassisch: Use-after-free).
  • Heap-Korruption: Meldungen wie „Invalid pointer operation“, „Access violation in ntdll“ oder Crashes beim Beenden/Finalisieren.
  • Regressionssuche: Nach Refactoring, Library-Update oder Compilerwechsel plötzlich neue Instabilität.

Nicht sinnvoll ist FullDebugMode als „wir schalten das mal in allen Builds ein“. Der Overhead ist hoch, das Timing ändert sich, und gerade Race-Conditions können dadurch verschwinden oder sich verschieben. Für Dauerbetrieb taugt eher ein schlankes Monitoring (z. B. Prozess-Working-Set, Private Bytes, Zähler pro Vorgang) – FullDebugMode ist das Skalpell, nicht der Pulsfühler.

Grundprinzip: Leak-Report ist Symptom, Stacktrace ist Spur

Ein Leak-Report zeigt dir zuerst einmal: Diese Blöcke sind am Programmende noch allokiert. Das ist nur dann automatisch ein Problem, wenn diese Blöcke eigentlich hätten freigegeben werden müssen. Es gibt legitime „Leaks“: globale Singletons, Caches, OS-Handles mit Prozess-Lifetime oder third-party Bibliotheken, die absichtlich nicht finalisieren. Diese Fälle willst du kennen, aber nicht blind „wegfixen“.

Der Stacktrace im Report zeigt die Stelle, an der der Block angefordert wurde. Das ist oft nicht der Ort, an dem du „Free vergessen“ hast. Häufige Realität in gewachsenen Systemen:

  • Allokation im UI- oder Service-Layer, Freigabe sollte in tieferer Schicht passieren (Ownership unklar).
  • Allokation in einer Factory, Ownership wird an den Caller übergeben – aber der Caller denkt, es wäre „owned“.
  • Objekte werden in Collections gehalten (Listen, Dictionaries), aber das Ownership-Model ist nicht konsistent.
  • Ein Exception-Pfad überspringt Cleanup, weil try/finally fehlt oder zu spät beginnt.

Der saubere Ablauf ist daher: reproduzierenisolierenStacktrace auflösenOwnership-Fehler findenFix mit Regressionstest. FastMM liefert dir die Spuren, aber du musst sie in Architektur und Lebenszyklen übersetzen.

FastMM FullDebugMode sauber aktivieren (ohne Nebenwirkungen zu übersehen)

Schematische Grafik mit Heap-Blöcken und Prüfrändern, die in einen Leak-Report überführt werden
Abstrakt: FullDebugMode arbeitet mit zusätzlichen Prüfrändern und Report-Ausgabe.

Der FullDebugMode wird in der Praxis über die FastMM-Optionen und eine passende FastMM-Konfiguration aktiviert. Entscheidend ist weniger „wie genau heißt die Include-Datei“, sondern was die Konfiguration bewirkt und unter welchen Build-Bedingungen du sie nutzt.

Empfohlene Rahmenbedingungen für den Debug-Build

  • Debug DCUs und Debug-Infos: Stacktraces sind nur nützlich, wenn sie auf echte Unit/Zeile/Adresse auflösbar sind. Stelle sicher, dass Debug-Informationen erzeugt werden und eine MAP-Datei verfügbar ist.
  • Optimierung bewusst wählen: Für Stacktrace-Lesbarkeit ist ein nicht-optimierter Build meist besser. Inlining und aggressive Optimierung können Stackframes „verwischen“.
  • Gleiche Laufzeitbedingungen: Nutze möglichst gleiche Daten, gleiche Konfiguration, gleiche Rechte. Viele Leaks sind datenabhängig (z. B. seltene Formate, Sonderpfade).
  • 64-bit vs. 32-bit trennen: Speicherverhalten, Alignment und Drittbibliotheken unterscheiden sich. Debugge in der Zielplattform, in der das Problem auftritt.

Ein Punkt, den Admins und technische Leads oft unterschätzen: FullDebugMode kann auch Timing verändern. Wenn du Threading im Spiel hast, können Race-Conditions dadurch anders auftreten. Deshalb ist es sinnvoll, parallel auch einen Lauf ohne FullDebugMode zu haben, der nur die Reproduktion bestätigt. FullDebugMode ist dann der Schritt zur Diagnose.

Vorsicht bei „ReportMemoryLeaksOnShutdown“

Delphi kann über ReportMemoryLeaksOnShutdown Leaks beim Programmende melden. Das ist praktisch, aber in komplexen Anwendungen (Services, Plug-in Host, lange Laufzeiten) kann es trügen: Beim Shutdown laufen Finalization-Abschnitte, Threads stoppen, Caches werden weggeräumt. Ein Leak, das in der Mitte der Laufzeit kritisch ist, kann bis zum Ende wieder verschwinden – oder umgekehrt: ein scheinbarer Leak entsteht erst im Shutdown, weil noch Hintergrundarbeit läuft.

Für praxistaugliches Leak-Hunting ist daher wichtiger: Leak pro Vorgang messen (z. B. nach 100 Requests), nicht nur beim Beenden. FastMM kann dabei helfen, aber der Testaufbau muss das abbilden.

Der typische Randfall: Leak-Report zeigt „irgendein Objekt“, aber die Ursache ist Ownership

Ein Klassiker aus Unternehmensanwendungen: Ein Importprozess erzeugt pro Datensatz Hilfsobjekte (z. B. StringLists, JSON-Parser, Temporärlisten). Im Happy Path wird sauber freigegeben. In seltenen Fällen (validierungsbedingter Skip, Exception, frühes Exit) bleibt ein Objekt hängen. Nach 10.000 Datensätzen ist das sichtbar.

FastMM FullDebugMode hilft hier, weil er die Allokationsstelle zeigt. Aber der „Fix“ ist nicht „free an die Stelle der Allokation“. Der Fix ist ein robustes Ownership-Pattern:

  • Wer erzeugt ein Objekt, ist nicht automatisch Owner.
  • Ownership muss im API-Vertrag klar sein (Parameter/Return, Dokumentation, Namenskonventionen).
  • Collections müssen eindeutig sein: owning vs. non-owning. Mischformen rächen sich.
  • Exception-Pfade brauchen frühe try/finally-Klammern.

Wenn du aus dem Stacktrace nur „TStringList.Create“ siehst, ist die Information nicht wertlos – aber sie sagt dir nur: hier entsteht etwas. Die Frage ist: wo sollte es enden? Und da hilft Architekturdenken mehr als Debugger-Akrobatik.

Stacktraces korrekt lesen: Was du wirklich daraus ableiten kannst

Detailaufnahme einer Debugging-Analyse mit unscharfem Debugger und handnotierter Call-Chain
Beim Stacktrace zählt die Call-Chain – nicht die einzelne Zeile.

Ein Stacktrace aus FastMM ist in der Regel eine Liste von Rücksprungadressen, die – mit Debug-Symbolen – auf Units, Prozeduren und idealerweise Zeilennummern abgebildet werden. Wenn du das liest, sind drei Dinge entscheidend:

  • Top-of-Stack ist nicht immer der Fehler: Die obersten Frames sind oft Memory-Manager/RTL. Interessant wird es dort, wo dein Code beginnt.
  • Call-Chain statt Einzelzeile: Die Zeile ist nur ein Punkt. Die Kette zeigt dir, welcher Pfad zu der Allokation führte.
  • Mehrere identische Blöcke: Wenn FastMM mehrere Leaks gleicher Größe meldet, ist das oft ein wiederkehrender Pfad. Das ist gut: du hast Reproduzierbarkeit.

Wenn Zeilennummern fehlen: MAP-Datei, Packages, Release-DCUs

Viele Teams stolpern an der Stelle: FullDebugMode ist aktiv, der Leak-Report kommt, aber statt Unit/Zeile gibt es nur Adressen oder kryptische Symbole. Typische Ursachen:

  • Keine MAP-Datei oder Debug-Infos wurden nicht erzeugt.
  • Du läufst gegen Release-DCUs oder Dritt-DLLs ohne Symbole.
  • Die Anwendung nutzt Runtime Packages: dann liegen Teile des Codes in BPLs, und die Symbolauflösung muss dazu passen.
  • Optimierung/Inlining hat den Stacktrace unleserlicher gemacht.

In der Praxis bedeutet das: Für die Leak-Jagd brauchst du einen Build, der bewusst „diagnosefähig“ ist. Das ist ein anderes Ziel als „möglichst schnell“. Technische Leads sollten das als eigenes Build-Profil behandeln, damit nicht jedes Teammitglied ad hoc Projektoptionen verstellt.

Frames bewerten: „Interessant“ ist oft eine Zeile weiter oben

Ein Beispiel aus der Realität (ohne konkreten Kundencode): Der Stacktrace zeigt dir als ersten Frame in deinem Code eine Routine „LoadConfig“. Du siehst dort eine Objekt-Erzeugung. Du fügst ein Free hinzu, der Leak ist weg – und plötzlich knallt es an anderer Stelle mit Double Free. Warum? Weil „LoadConfig“ das Objekt in einen Cache legt, und ein anderer Codepfad ist bereits Owner und räumt später auf.

Die richtige Lesart wäre gewesen: Der Stacktrace zeigt dir, wo der Block entsteht. Der Fix liegt häufig in der Definition: Wer besitzt das Objekt nach dem Return? Wenn du diese Frage nicht sauber beantwortest, wechselst du nur das Fehlerbild (Leak → 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
Heap-Korruption zeigt sich oft zeitversetzt – FullDebugMode macht sie früher sichtbar.

Viele „Leaks“ sind in Wahrheit Folgeprobleme: Ein Buffer-Overrun überschreibt Heap-Metadaten, der Memory-Manager kann später nicht sauber freigeben, und am Ende siehst du scheinbar zufällige Leaks oder Invalid Pointer Operations. FullDebugMode ist hier stark, weil er mit Prüfmustern arbeitet und bei Free/Reuse zusätzliche Validierungen macht.

Wichtig ist die Unterscheidung:

  • Leak: Block wurde allokiert und nie freigegeben. Stabilität leidet über Zeit, Crash ist nicht zwingend.
  • Use-after-free: Block wird freigegeben, aber später noch benutzt. Führt zu sporadischen AVs, die schwer reproduzierbar sind.
  • Double Free: Block wird zweimal freigegeben. Kann sofort knallen oder erst später (wenn der Block wiederverwendet wurde).
  • Heap-Korruption: Irgendwer schreibt über die Grenzen eines Blocks. Symptome sind oft zeitversetzt.

FullDebugMode ist besonders wertvoll, wenn du Symptome zeitversetzt siehst. Die zusätzliche Validierung macht Fehler früher sichtbar – oft genau an der Stelle, an der der falsche Zugriff passiert, nicht erst Minuten später in einem beliebigen Free.

Vorgehen in Projekten: Reproduzierbare Leak-Jagd statt „Debugging im Nebel“

Wenn du Speicherlecks jagen willst, brauchst du einen Ablauf, der wiederholbar ist und sich in Teams teilen lässt. Ich arbeite gern mit einem festen Diagnoserahmen:

1) Reproduktion in einem deterministischen Szenario

Lege eine Testsequenz fest, die den Leak zuverlässig zeigt: „Starte Service, verarbeite 500 Nachrichten, stoppe Service“ oder „Öffne Maske X, führe Aktion Y 200-mal aus“. Wichtig ist, dass du die Sequenz mit Parametern dokumentierst (Datenbestand, Mandant, Feature-Flags), damit andere sie nachstellen können.

2) Minimieren: Leak pro Schritt sichtbar machen

Wenn die Sequenz 20 Minuten dauert, teile sie. Ziel ist: du willst möglichst schnell zwischen „vorher“ und „nachher“ vergleichen. In großen Anwendungen ist das oft der eigentliche Zeitfresser, nicht das Fixen.

3) FullDebugMode einschalten und Report interpretieren

Jetzt erst kommt FastMM FullDebugMode ins Spiel. Sammle die Reports, gruppiere nach Blockgröße/Callstack und schaue nach Wiederholungen. Ein einzelner verbleibender Block kann ein legitimer Cache sein. 10.000 identische Blöcke sind fast immer ein echter Leak.

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

Fixe Leaks dort, wo Ownership definiert wird: Factory, API-Vertrag, Collection-Wrapper. „Schnell ein Free einbauen“ direkt neben Create ist oft der falsche Ort, wenn das Objekt weitergereicht wird.

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

Der Fix ist erst dann gut, wenn die Sequenz wieder läuft und weder Leaks noch neue Speicherfehler auftreten. Gerade bei Use-after-free ist ein „Leak weg“ kein Beweis, sondern nur ein neues Symptom.

Typische Fallstricke in Delphi-Code, die FastMM sichtbar macht

Collections und Ownership (Listen, Dictionaries, Interfaces)

Viele Leaks kommen nicht aus komplizierten Algorithmen, sondern aus alltäglichen Datenstrukturen. Zwei klassische Fehlerbilder:

  • Eine Liste enthält Objekte, aber niemand weiß, wer sie freigibt. Lösung: owning Liste verwenden oder konsequent im finally räumen.
  • Ein Dictionary hält Objekte als Values; beim Remove wird der Value nicht freigegeben oder beim Clear vergessen.

Zusätzlich tricky sind Interfaces: Referenzzählung (ARC-ähnlich) ist komfortabel, aber Mischbetrieb mit Objekt-Ownership kann bei zyklischen Referenzen oder Events Leaks erzeugen. FullDebugMode zeigt dir dann oft den Allokationspfad, aber die Ursache ist ein Referenzzyklus (A hält B über Interface, B hält A über Callback).

Exceptions und frühe Exits

In gewachsenen Business-Software-Systemen sind Exceptions oft Teil der normalen Steuerung (z. B. Validierung, Abbruch, Retry). Das Problem ist selten die Exception selbst, sondern der Pfad drumherum: Ein Objekt wird vor dem try/finally erzeugt, dann fliegt eine Exception, und der Cleanup wird übersprungen. FullDebugMode liefert dir den Stacktrace der Allokation – und du musst prüfen, ob es einen garantiert ausgeführten Freigabepfad gibt.

Threads und Lebenszeit: „Freigeben im falschen Thread“

Bei VCL/FMX und Services mit Worker-Threads entsteht ein weiterer Randfall: Ein Objekt wird in einem Thread erzeugt, aber im UI-Thread freigegeben (oder umgekehrt), weil man per Queue/Synchronize „nur schnell“ etwas rüberreicht. Das kann funktionieren, kann aber auch zu Use-after-free führen, wenn der Producer weiterarbeitet, während der Consumer bereits freigibt.

FastMM FullDebugMode kann hier helfen, weil er zeitversetzte Fehler früher detektiert. Der eigentliche Fix ist jedoch ein sauberes Lebenszeitmodell: klare Besitzverhältnisse, Übergabe nur über immutable Daten oder eindeutige Ownership-Transfer-Punkte.

Wie du Reports nutzbar machst: Filtern, vergleichen, dokumentieren

In Teams lohnt es sich, Leak-Reports nicht nur „anzuschauen“, sondern wie ein Artefakt zu behandeln. Drei pragmatische Maßnahmen, die sich bewährt haben:

  • Baseline-Report: Ein „bekannter Zustand“ (z. B. aktuelle Produktversion) wird einmal mit FullDebugMode durchlaufen und als Referenz abgelegt. Dann erkennst du neue Leaks sofort.
  • Vergleich nach Use-Case: Für kritische Workflows (Import, Export, API-Request, UI-Massenoperation) legst du jeweils eine kurze Sequenz fest, die regelmäßig wiederholbar ist.
  • Dokumentierte „legitime Leaks“: Wenn ein Cache bewusst nicht finalisiert, dokumentiere das. Sonst jagt in sechs Monaten jemand wieder die gleichen Einträge.

Das ist keine Bürokratie, sondern eine Zeitersparnis: Leak-Jagd wird sonst schnell zur Endlosschleife, weil die gleichen Muster in jedem Sprint erneut auftreten.

Wann sich der Aufwand lohnt – und wann du anders vorgehen solltest

FastMM FullDebugMode ist ein Diagnosewerkzeug mit Kosten. Der Aufwand lohnt sich besonders, wenn:

  • Die Anwendung lange läuft (Service, Terminalserver-Client, Schichtsystem, 24/7-Prozesse).
  • Du reale Kundendatenströme verarbeitest und nicht alle Pfade im Test abdeckst.
  • Stabilität wichtiger ist als kurzfristige Feature-Geschwindigkeit (typisch in prozessnahen Softwarelösungen).

Wenn du dagegen nur einen kleinen Desktop-Helper hast, der nach 30 Sekunden wieder endet, ist die Leak-Jagd oft zweitrangig. Ebenso: Wenn du ein einmaliges Memory-Spike-Problem hast (z. B. großer Export), ist es häufig kein Leak, sondern eine Frage der Streaming-Strategie und der Peak-Last im Heap.

Praxisfazit: FullDebugMode ist kein Schalter, sondern ein Prozess

FastMM FullDebugMode bringt Struktur in die Speicherfehlersuche: Er macht Allokationen sichtbar, deckt Heap-Korruption früher auf und liefert Stacktraces, mit denen du Ursache statt Symptom fixen kannst. Der entscheidende Hebel ist aber nicht das Tool, sondern der Ablauf: reproduzierbare Szenarien, diagnosefähige Builds, saubere Ownership-Verträge und Regression gegen eine Baseline.

Wenn du an einem hartnäckigen Leak oder sporadischen Heap-Fehler festhängst und das Thema in einem größeren Delphi-System nachhaltig stabilisieren willst, lohnt sich ein kurzer, sauberer Diagnose-Setup mit klarer Sequenz und auswertbaren Reports. Wenn du dafür Unterstützung bei Analyse, Build-Profilen oder Architektur-Refactoring brauchst: Kontakt zur Net-Base Software GmbH.

Für dieses Thema sind auch Delphi Speicherleck Finden und Fastmm Leak Report Lesen wichtig. Der Beitrag ordnet diese Aspekte verständlich ein und zeigt, worauf es im Alltag ankommt.

Projekt oder Modernisierungsvorhaben mit Net-Base besprechen.

Pas següent

Quan d'un tema se'ndevé un projecte real, s'han de considerar aviat i de manera conjunta l'arquitectura, els actius existents i l'operació.

No només donem suport en qüestions puntuals, sinó també quan, a partir de fragments de codi font, temes de sistemes heredats o idees de portal, ha de sorgir un projecte empresarial sòlid.

  • L'estat actual, la visió objectiu i els riscos tècnics s'avaluen conjuntament.
  • REST, accés a dades, portals i desplegament no es posposaran com a efectes retardats.
  • Veu aviat quin camí és viable des del punt de vista econòmic i operatiu.

Comparteix la publicació

Comparteix aquesta publicació directament

LinkedIn, X, XING, Facebook, WhatsApp i correu electrònic estan disponibles immediatament. Per Instagram preparem l'enllaç i un text curt immediatament.

Correu electrònic

Instagram s'obre en una pestanya nova. L'enllaç i el text curt es copien prèviament al porta-retalls.