Van magazinethema naar projectpraktijk
Relevante dienst- en technische pagina's bij het artikel
Als een Delphi-toepassing in productie langzaam „opzwelt“, sporadisch met Access Violations crasht of na dagen draaien plotseling instabiel wordt, zit daar vaak geen enkele bug achter, maar een patroon: geheugen wordt aangevraagd maar niet netjes vrijgegeven — of het wordt te vroeg vrijgegeven en later nog gebruikt. Precies daar is FastMM FullDebugMode goud waard. Niet als permanente modus, maar als gericht diagnose-instrument dat van „ergens in de heap is iets kapot“ weer een herleidbare oorzaak maakt.
De keerzijde: FullDebugMode genereert veel output, kost performance en leidt snel tot misinterpretaties. Een leak-report toont niet automatisch de plek waar „de fout“ zit. En een stacktrace is slechts zo goed als de symbol-resolutie (MAP-bestand, debug-info, inlining). In dit artikel bespreek ik de typische randgevallen, leg ik de nette aanpak uit en wijs ik op valkuilen — zodat je uiteindelijk niet alleen leaks vindt, maar ze duurzaam verhelpt.
Wanneer FastMM FullDebugMode echt zinvol is
FastMM is in moderne Delphi-versies vaak al de default-memory-manager of wordt in veel projecten toch al gebruikt. De FullDebugMode is echter een speciale configuratie: hij voorziet geheugenblokken van extra controlepatronen, verzamelt allocatie-stacktraces en controleert agressiever op heap-corruptie (dus beschadigde beheersdata in de heap, bijv. door buffer-overruns).
Ik gebruik FullDebugMode doelgericht wanneer een van de volgende situaties zich voordoet:
- Reproduceerbaar leak: het geheugenverbruik stijgt tijdens een testrun per operatie (bijv. per request, per import, per UI-actie).
- Sporadische AVs: vooral die welke „soms hier, soms daar“ in hetzelfde gebied voorkomen (klassiek: Use-after-free).
- Heap-corruptie: meldingen zoals „Invalid pointer operation“, „Access violation in ntdll“ of crashes bij afsluiten/finaliseren.
- Regressies zoeken: na refactoring, library-update of compilerwissel plotseling nieuwe instabiliteit.
FullDebugMode is niet geschikt als „we zetten het maar in alle builds aan“. De overhead is hoog, timings veranderen en juist race-conditions kunnen daardoor verdwijnen of verplaatsen. Voor continu gebruik is een slanker monitoring geschikt (bijv. proces Working Set, Private Bytes, tellers per operatie) — FullDebugMode is het scalpel, niet de polsslagmeter.
Basisprincipe: leak-report is symptoom, stacktrace is spoor
Een leak-report laat je eerst zien: deze blokken zijn aan het einde van het programma nog gealloceerd. Dat is alleen automatisch een probleem als die blokken eigenlijk hadden moeten worden vrijgegeven. Er zijn legitieme „leaks“: globale singletons, caches, OS-handles met proces-lifetime of third-party bibliotheken die opzettelijk niet finaliseren. Deze gevallen wil je kennen, maar niet blindelings „wegfixen“.
De stacktrace in het rapport toont de plek waar het blok werd aangevraagd. Dat is vaak niet de plek waar je „Free bent vergeten“. Veelvoorkomende realiteit in verouderde systemen:
- Allocatie in de UI- of servicelaag, vrijgave zou in een dieper liggende laag moeten gebeuren (ownership onduidelijk).
- Allocatie in een factory, ownership wordt aan de caller overgedragen — maar de caller denkt dat het „owned“ is.
- Objecten worden in collecties gehouden (lijsten, dictionaries), maar het ownership-model is niet consistent.
- Een exception-pad slaat cleanup over omdat try/finally ontbreekt of te laat begint.
De juiste werkwijze is daarom: reproduceren → isoleren → stacktrace ontleden → ownership-fout vinden → oplossing met regressietest. FastMM levert je de sporen, maar je moet ze vertalen naar architectuur en levenscycli.
FastMM FullDebugMode netjes inschakelen (zonder bijwerkingen over het hoofd te zien)
In de praktijk wordt FullDebugMode ingeschakeld via de FastMM-opties en een passende FastMM-configuratie. Beslissend is minder „hoe heet het include-bestand precies“, maar wat de configuratie doet en onder welke buildvoorwaarden je hem gebruikt.
Aanbevolen randvoorwaarden voor de Debug-Build
- Debug-DCUs en debug-informatie: stacktraces zijn alleen nuttig als ze naar echte unit/regel/adres te vertalen zijn. Zorg dat debug-informatie wordt gegenereerd en dat een MAP-bestand beschikbaar is.
- Optimalisatie bewust kiezen: voor leesbaarheid van stacktraces is een niet-geoptimaliseerde build meestal beter. Inlining en agressieve optimalisatie kunnen stackframes vervagen.
- Gelijke runtime-condities: gebruik bij voorkeur dezelfde data, dezelfde configuratie, dezelfde rechten. Veel leaks zijn data-afhankelijk (bijv. zeldzame formaten, bijzondere paden).
- 64-bit vs. 32-bit scheiden: geheugengedrag, alignment en third-party bibliotheken verschillen. Debug in het doelsysteem waarop het probleem zich voordoet.
Een punt dat admins en technische leads vaak onderschatten: FullDebugMode kan ook timing veranderen. Als threading in het spel is, kunnen raceconditions daardoor anders optreden. Daarom is het zinvol om daarnaast een run zonder FullDebugMode te hebben die alleen de reproductie bevestigt. FullDebugMode is dan de stap voor diagnose.
Voorzichtigheid bij „ReportMemoryLeaksOnShutdown“
Delphi kan via ReportMemoryLeaksOnShutdown leaks bij het afsluiten melden. Dat is praktisch, maar in complexe applicaties (services, plug-in host, lange runtimes) kan het misleiden: tijdens shutdown lopen finalization-secties, threads stoppen, caches worden opgeschoond. Een leak dat halverwege de runtime kritisch is, kan tegen het einde weer verdwijnen – of omgekeerd: een schijnbaar leak ontstaat pas bij shutdown omdat er nog achtergrondwerk loopt.
Voor praktisch bruikbare Leak-Hunting is het daarom belangrijker: leak per operatie meten (bijv. na 100 requests), niet alleen bij het afsluiten. FastMM kan daarbij helpen, maar de testopzet moet dat afbeelden.
Het typische randgeval: het leak-rapport toont „een willekeurig object“, maar de oorzaak is Ownership
Een klassieker in bedrijfsapplicaties: een importproces creëert per record hulpprogramma-objecten (bijv. StringLists, JSON-parsers, tijdelijke lijsten). In het Happy Path wordt dit netjes vrijgegeven. In zeldzame gevallen (door validatie veroorzaakte Skip, Exception, vroegtijdige Exit) blijft een object hangen. Na 10.000 records wordt dat zichtbaar.
FastMM FullDebugMode helpt hier, omdat hij de allocatieplaats toont. Maar de „Fix“ is niet „free op de plek van de allocatie“. De oplossing is een robuust Ownership-Pattern:
- Wie een object creëert, is niet automatisch de Owner.
- Ownership moet in het API-contract duidelijk zijn (Parameter/Return, documentatie, naamconventies).
- Collecties moeten eenduidig zijn: owning vs. non-owning. Mengvormen wreken zich.
- Exception-paden vereisen vroege try/finally-blokken.
Als je uit de stacktrace alleen „TStringList.Create“ ziet, is de informatie niet waardeloos – maar het zegt je alleen: hier wordt iets aangemaakt. De vraag is: waar zou het moeten eindigen? En daarbij helpt architectuurdenken meer dan debugger-acrobatiek.
Stacktraces correct lezen: wat je er echt uit kunt afleiden
Een stacktrace van FastMM is doorgaans een lijst van terugkeeradressen die – met debug-symbolen – worden afgebeeld op Units, Prozeduren en idealiter regelnummers. Als je dat leest, zijn drie zaken doorslaggevend:
- Top-of-Stack is niet altijd de fout: de bovenste frames zijn vaak Memory-Manager/RTL. Interessant wordt het waar jouw code begint.
- Call-Chain in plaats van een enkele regel: de regel is slechts een punt. De keten toont welk pad naar de allocatie leidde.
- Meerdere identieke blokken: als FastMM meerdere leaks van dezelfde grootte meldt, is dat vaak een terugkerend pad. Dat is goed: je hebt reproduceerbaarheid.
Als regelnummers ontbreken: MAP-Datei, Packages, Release-DCUs
Veel teams struikelen hier: FullDebugMode is actief, het Leak-Report verschijnt, maar in plaats van Unit/Zeile zijn er alleen adressen of cryptische symbolen. Typische oorzaken:
- Geen MAP-bestand of er zijn geen Debug-Infos gegenereerd.
- Je draait tegen Release-DCUs of derde-partij DLLs zonder symbolen.
- De applicatie gebruikt Runtime Packages: dan liggen delen van de code in BPLs, en de symbolresolutie moet daarbij passen.
- Optimalisierung/Inlining heeft de stacktrace onleesbaarder gemaakt.
In de praktijk betekent dit: voor de leak-jacht heb je een build nodig die bewust „diagnosefähig“ is. Dat is een ander doel dan „zo snel mogelijk“. Technische Leads zouden dit als een apart build-profiel moeten behandelen, zodat niet elk teamlid ad hoc projectopties verstelt.
Frames beoordelen: „Interessant“ ligt vaak een regel hoger
Een voorbeeld uit de praktijk (zonder concrete klantcode): de stacktrace toont je als eerste frame in jouw code een routine „LoadConfig“. Je ziet daar een objectcreatie. Je voegt een Free toe, het geheugenlek is weg – en plots knalt het elders met een double free. Waarom? Omdat „LoadConfig“ het object in een cache plaatst, en een ander codepad al eigenaar is en later opruimt.
De juiste lezing zou geweest zijn: de stacktrace laat je zien, waar het blok ontstaat. De fix ligt vaak in de definitie: wie bezit het object na de return? Als je die vraag niet netjes beantwoordt, verander je alleen het foutbeeld (geheugenlek → AV).
Heap-corruptie vs. geheugenlek: waarom FullDebugMode vaak de echte schuldige vindt
Veel „leaks“ zijn in werkelijkheid gevolgproblemen: een buffer-overrun overschrijft heap-metadata, de memory-manager kan later niet netjes vrijgeven, en uiteindelijk zie je schijnbaar willekeurige geheugenlekken of ongeldige pointeroperaties. FullDebugMode is hier krachtig, omdat het met testpatronen werkt en bij Free/Reuse extra validaties uitvoert.
Belangrijk is het onderscheid:
- Geheugenlek: het blok is gealloceerd en nooit vrijgegeven. Stabiliteit lijdt in de loop van de tijd; een crash is niet per se het gevolg.
- Use-after-free: het blok wordt vrijgegeven, maar later nog gebruikt. Leidt tot sporadische AVs die moeilijk reproduceerbaar zijn.
- Double Free: het blok wordt twee keer vrijgegeven. Kan direct knallen of pas later (als het blok hergebruikt is).
- Heap-corruptie: iemand schrijft over de grenzen van een blok. Symptomen treden vaak met vertraging op.
FullDebugMode is bijzonder waardevol als je symptomen met vertraging ziet. De extra validatie maakt fouten eerder zichtbaar – vaak precies op de plek waar de verkeerde toegang plaatsvindt, en niet pas minuten later in een willekeurige Free.
Werkwijze in projecten: reproduceerbare jacht op geheugenlekken in plaats van „Debugging im Nebel“
Als je geheugenlekken wilt opsporen, heb je een procedure nodig die herhaalbaar is en binnen teams gedeeld kan worden. Ik werk graag met een vast diagnosekader:
1) Reproductie in een deterministisch scenario
Stel een testsequentie vast die het geheugenlek betrouwbaar aantoont: „Start service, verwerk 500 berichten, stop service“ of „Open scherm X, voer actie Y 200 keer uit“. Belangrijk is dat je de sequentie met parameters documenteert (databestand, tenant, feature-flags), zodat anderen deze kunnen reproduceren.
2) Minimaliseren: het lek per stap zichtbaar maken
Als de sequentie 20 minuten duurt, deel die op. Het doel is dat je zo snel mogelijk tussen „voor“ en „na“ wilt vergelijken. In grote applicaties is dat vaak de werkelijke tijdvreter, niet het fixen.
3) FullDebugMode inschakelen en het rapport interpreteren
Nu komt pas FastMM FullDebugMode om de hoek kijken. Verzamel de rapporten, groepeer ze op blokgrootte/Callstack en kijk naar herhalingen. Een enkel achtergebleven blok kan een legitieme cache zijn. 10.000 identieke blokken zijn bijna altijd een echte leak.
4) Eigenaarschap bepalen en fix in de juiste laag
Los leaks op op de plek waar het eigenaarschap wordt gedefinieerd: Factory, API-contract, Collection-Wrapper. „Snel een Free toevoegen“ direct naast Create is vaak de verkeerde plek als het object wordt doorgegeven.
5) Regressie: dezelfde Sequenz, dezelfde Build, hetzelfde Report
De fix is pas goed als de sequentie weer draait en noch leaks noch nieuwe geheugenfouten optreden. Vooral bij Use-after-free is een „Leak weg“ geen bewijs, maar slechts een nieuw symptoom.
Veelvoorkomende valkuilen in Delphi-Code, die FastMM zichtbaar maakt
Collecties en eigenaarschap (lijsten, dictionaries, interfaces)
Veel leaks ontstaan niet door ingewikkelde algoritmen, maar door alledaagse datastructuren. Twee klassieke foutpatronen:
- Een lijst bevat objecten, maar niemand weet wie ze vrijgeeft. Oplossing: een owning-lijst gebruiken of consequent opruimen in finally.
- Een dictionary houdt objecten als values; bij Remove wordt de value niet vrijgegeven of bij Clear vergeten.
Daarnaast zijn Interfaces tricky: referentietelling (ARC-achtig) is comfortabel, maar gemengde werking met object-ownership kan bij cyclische referenties of events leaks veroorzaken. FullDebugMode toont je dan vaak het allocatiepad, maar de oorzaak is een referentiecyclus (A houdt B via Interface, B houdt A via callback).
Exceptions en vroege Exits
In volwassen Business-Software-systemen zijn Exceptions vaak onderdeel van de normale sturing (bijv. validatie, afbreken, retry). Het probleem is zelden de Exception zelf, maar het pad daaromheen: een object wordt vóór de try/finally aangemaakt, dan treedt een Exception op en wordt de cleanup overgeslagen. FullDebugMode levert je de stacktrace van de allocatie – en je moet controleren of er een gegarandeerd uitgevoerd vrijgavepad is.
Threads en levensduur: „Vrijgeven in de verkeerde Thread“
Bij VCL/FMX en services met worker-threads ontstaat een randgeval: een object wordt in één thread aangemaakt, maar in de UI-thread vrijgegeven (of andersom), omdat men per Queue/Synchronize „even snel“ iets doorgeeft. Dat kan werken, maar kan ook tot Use-after-free leiden als de Producer verderwerkt terwijl de Consumer al vrijgeeft.
FastMM FullDebugMode kan hierbij helpen, omdat hij tijdverschuivende fouten eerder detecteert. De daadwerkelijke fix is echter een schoon levensduurmodel: duidelijke eigendomsverhoudingen, overdracht alleen via immutable data of ondubbelzinnige ownership-transferpunten.
Hoe je rapporten bruikbaar maakt: filteren, vergelijken, documenteren
In teams is het de moeite waard om Leak-Reports niet alleen „te bekijken“, maar als een artefact te behandelen. Drie pragmatische maatregelen die zich hebben bewezen:
- Baseline-Report: Een „bekende toestand“ (bijv. huidige productversie) wordt één keer met FullDebugMode doorlopen en als referentie opgeslagen. Daarmee herken je nieuwe leaks onmiddellijk.
- Vergelijking per Use-Case: Voor kritieke workflows (Import, Export, API-Request, UI-Massenoperation) leg je per geval een korte, herhaalbare sequentie vast.
- Gedocumenteerde „legitieme Leaks“: Als een cache bewust niet wordt gefinaliseerd, documenteer dat. Anders jaagt over zes maanden iemand opnieuw dezelfde items na.
Dit is geen bureaucratie, maar een tijdsbesparing: de jacht op geheugenlekken wordt anders snel een eindeloze lus, omdat dezelfde patronen in elke sprint opnieuw optreden.
Wanneer de inspanning de moeite waard is – en wanneer je anders te werk moet gaan
FastMM FullDebugMode is een diagnosehulpmiddel met kosten. De inspanning loont vooral als:
- De applicatie lange draait (Service, Terminalserver-Client, ploegendienstsysteem, 24/7-processen).
- Je echte klantendatastromen verwerkt en niet alle paden in tests dekt.
- Stabiliteit belangrijker is dan kortetermijnsnelheid voor features (typisch bij procesgebonden softwareoplossingen).
Als je daarentegen slechts een kleine Desktop-Helper hebt die na 30 seconden weer eindigt, is de jacht op geheugenlekken vaak secundair. Evenzo: als je een eenmalig geheugenpiekprobleem hebt (bijv. grote export), is het vaak geen lek maar een kwestie van streamingstrategie en piekbelasting in de heap.
Praktijkconclusie: FullDebugMode is geen schakel, maar een proces
FastMM FullDebugMode brengt structuur in het zoeken naar geheugenfouten: het maakt allocaties zichtbaar, detecteert heap-corruptie eerder en levert stacktraces waarmee je de oorzaak in plaats van het symptoom kunt verhelpen. De beslissende hefboom is echter niet het hulpmiddel, maar het proces: reproduceerbare scenario’s, diagnoseerbare builds, duidelijke ownership-overeenkomsten en regressietests tegen een baseline.
Als je vastzit aan een hardnekkig lek of sporadische heap-fout en dit onderwerp in een groter Delphi-systeem duurzaam wilt stabiliseren, is een korte, nette Diagnose-Setup met een heldere volgorde en analyseerbare rapporten de moeite waard. Als je daarbij ondersteuning nodig hebt bij analyse, Build-Profilen of Architectur-Refactoring: neem contact op met Net-Base Software GmbH.
Voor dit onderwerp zijn ook Delphi Speicherleck Finden en Fastmm Leak Report Lesen belangrijk. Het artikel plaatst deze aspecten begrijpelijk en laat zien waar het in de praktijk om gaat.
volgende stap
Wanneer het onderwerp een concreet project wordt, moeten architectuur, bestaande omgeving en exploitatie vroegtijdig samen worden bekeken.
We ondersteunen niet alleen bij individuele vragen, maar ook wanneer uit broncodefragmenten, legacy-onderwerpen of portalideeën een robuust bedrijfsproject moet ontstaan.
- Huidige situatie, doelbeeld en technische risico's worden gezamenlijk beoordeeld.
- REST, toegang tot gegevens, portalen en rollout worden niet naar latere fasen verschoven.
- U ziet vroeg welke weg economisch en operationeel levensvatbaar is.