Fra magasinets tema til projektpraksis
Passende service- og tekniske sider til artiklen
Hvis en Delphi-applikation i drift gradvist bruger mere og mere hukommelse, sporadisk styrter med Access Violations eller efter flere dages kørsel pludselig bliver ustabil, er der ofte ikke tale om en enkelt fejl, men om et mønster: Hukommelse allokeres, men frigives ikke korrekt – eller den frigives for tidligt og bruges senere. Her er FastMM FullDebugMode særdeles værdifuld. Ikke som permanent tilstand, men som et målrettet diagnoseværktøj, der gør „et eller andet i heap’en er galt“ til en sporbar årsag.
Problemet: FullDebugMode producerer meget output, koster ydeevne og fører hurtigt til fejltolkninger. En leak-rapport viser ikke automatisk det sted, hvor „fejlen“ er. Og en stacktrace er kun så god som symbolopløsningen (MAP-Datei, Debug-Infos, inlining). I dette indlæg gennemgår jeg det typiske kanttilfælde, forklarer den korrekte fremgangsmåde og faldgruberne – så du til sidst ikke blot finder leaks, men får dem lukket varigt.
Hvornår FastMM FullDebugMode virkelig er relevant
FastMM er i moderne Delphi-versioner ofte allerede standard-memory-manager eller integreres i mange projekter. FullDebugMode er dog en særlig konfiguration: Den markerer hukommelsesblokke med ekstra kontrolmønstre, indsamler allocations-stacktraces og kontrollerer mere aggressivt for heap-korruption (altså beskadigede administrationsdata i heap’en, f.eks. ved buffer-overruns).
Jeg anvender FullDebugMode målrettet, når et af følgende scenarier er til stede:
- Reproducerbart Leak: Hukommelsesforbruget stiger i et testrun pr. operation (f.eks. pr. Request, pr. Import, pr. UI-aktion).
- Sporadiske AVs: Især dem, der „nogle gange her, nogle gange dér“ opstår i samme område (klassisk: Use-after-free).
- Heap-korruption: Beskeder som „Invalid pointer operation“, „Access violation in ntdll“ eller crashes ved afslutning/finalisering.
- Søgning efter regressioner: Efter refaktorering, biblioteksopdatering eller compiler-skift opstår pludselig ny ustabilitet.
FullDebugMode giver ikke mening som en „vi aktiverer det i alle builds“-løsning. Overheadet er højt, timing ændrer sig, og race-conditions kan forsvinde eller flytte sig som følge heraf. Til permanent drift er et slankt overvågningssetup bedre egnet (f.eks. process Working Set, Private Bytes, tæller pr. operation) – FullDebugMode er skalpellens værktøj, ikke pulsføleren.
Grundprincippet: Leak-rapport er symptom, stacktrace er spor
En leak-rapport viser først og fremmest: dette blokke er stadig allokerede ved programmets afslutning. Det er kun automatisk et problem, hvis disse blokke faktisk burde være frigivet. Der findes legitime „lækager“: globale singletons, caches, OS-handles med proces-livstid eller tredjepartsbiblioteker, der bevidst ikke finaliserer. Disse tilfælde skal du kende, men ikke blint „fiche“.
Stacktracen i rapporten viser stedet, hvor blokken blev allokeret. Det er ofte ikke det sted, hvor du har „glemt at Free“. Hyppig realitet i voksende systemer:
- Allokering i UI- eller service-laget, frigivelsen burde ske i et dybere lag (ejerskab uklart).
- Allokering i en factory, ejerskabet overdrages til kaldende kode – men den kaldende kode antager, at den ejer objektet.
- Objekter holdes i collections (lister, dictionaries), men ejerskabsmodellen er ikke konsistent.
- En exceptionssti springer cleanup over, fordi try/finally mangler eller starter for sent.
Den korrekte arbejdsgang er derfor: reproducere → isolere → Stacktrace opløse → finde ejerskabsfejl → rettelse med regressionstest. FastMM leverer sporene, men du skal oversætte dem til arkitektur og livscyklusser.
Aktivér FastMM FullDebugMode korrekt (uden at overse sideeffekter)
FullDebugMode aktiveres i praksis via FastMM-indstillingerne og en passende FastMM-konfiguration. Mindre afgørende er „præcis hvad include-filen hedder“, vigtigere er hvad konfigurationen gør og under hvilke build-betingelser du bruger den.
Anbefalede rammebetingelser for debug-build
- Debug DCUs og debug-oplysninger: Stacktraces er kun nyttige, hvis de kan opløses til konkrete unit/linje/adresse. Sørg for, at debug-oplysninger genereres, og at en MAP-fil er tilgængelig.
- Vælg optimering med omtanke: For læsbarhed af stacktraces er en ikke-optimeret build ofte bedre. Inlining og aggressiv optimering kan stackframes udviske.
- Ensartede runtime-betingelser: Brug så vidt muligt de samme data, samme konfiguration, samme rettigheder. Mange leaks er dataafhængige (fx sjældne formater, særlige stier).
- Opdel 64-bit og 32-bit: Hukommelsesadfærd, alignment og tredjepartsbiblioteker adskiller sig. Debug på den målplatform, hvor problemet opstår.
Et punkt, som administratorer og tekniske ledere ofte undervurderer: FullDebugMode kan også timing ændre. Hvis der er tråde involveret, kan race conditions optræde anderledes. Derfor giver det mening at køre parallelt en gennemkørsel uden FullDebugMode, som kun bekræfter reproduktionen. FullDebugMode er så næste skridt i diagnostikken.
Forsigtighed ved „ReportMemoryLeaksOnShutdown“
Delphi kan via ReportMemoryLeaksOnShutdown rapportere leaks ved programafslutning. Det er praktisk, men i komplekse applikationer (services, plug-in host, lange køretider) kan det være vildledende: Under shutdown kører finalization-afsnit, tråde stopper, caches bliver ryddet. Et leak, der er kritisk midt i køretiden, kan være væk ved slutningen — eller omvendt: et tilsyneladende leak opstår først under shutdown, fordi baggrundsarbejde stadig kører.
For praktisk leak-hunting er det derfor vigtigere at måle leak per operation (fx efter 100 requests) og ikke kun ved afslutning. FastMM kan hjælpe med det, men testopbygningen skal afspejle det.
Det typiske kanttilfælde: Leak-rapporten viser „et eller andet objekt“, men årsagen er ejerskab
En klassiker i virksomhedsapplikationer: En importproces opretter for hver post hjælpeobjekter (f.eks. StringLists, JSON-Parser, midlertidige lister). På happy path frigives de korrekt. I sjældne tilfælde (valideringsbetinget skip, Exception, tidlig Exit) bliver et objekt hængende. Efter 10.000 poster bliver det synligt.
FastMM FullDebugMode hjælper her, fordi det viser allokeringsstedet. Men en „Fix“ er ikke at kalde „free“ på allokeringsstedet. Løsningen er et robust ownership-mønster:
- Den, der opretter et objekt, er ikke automatisk ejer.
- Ownership skal være klart defineret i API-kontrakten (Parameter/Return, dokumentation, navnekonventioner).
- Collections skal være entydige: owning vs. non-owning. Blandingsformer straffer sig.
- Exception-stier kræver tidlige try/finally-blokke.
Hvis du i stacktracet kun ser „TStringList.Create“, er informationen ikke værdiløs – men den siger dig kun: her opstår noget. Spørgsmålet er: hvor bør det ende? Og her hjælper arkitekturtænkning mere end debugger-akrobatik.
Stacktraces korrekt lesen: Was du wirklich daraus ableiten kannst
Et stacktrace fra FastMM er normalt en liste af returadresser, som – med debug-symboler – kan mappes til Units, procedurer og ideelt set linjenumre. Når du læser det, er tre ting afgørende:
- Top-of-Stack er ikke altid fejlen: De øverste frames er ofte Memory-Manager/RTL. Det bliver interessant der, hvor din kode begynder.
- Call-Chain i stedet for enkeltlinje: Linjen er kun et punkt. Kæden viser dig, hvilken sti der førte til allokationen.
- Flere identiske blokke: Hvis FastMM rapporterer flere leaks af samme størrelse, er det ofte en gentagen sti. Det er godt: du har reproducerbarhed.
Wenn Zeilennummern fehlen: MAP-Datei, Packages, Release-DCUs
Mange teams snubler her: FullDebugMode er aktiv, leak-rapporten kommer, men i stedet for Unit/Zeile er der kun adresser eller kryptiske symboler. Typiske årsager:
- Der blev ikke dannet en MAP-fil, eller debug-info blev ikke genereret.
- Du kører imod Release-DCUs eller tredjeparts-DLLs uden symboler.
- Applikationen bruger runtime-pakker: Så ligger dele af koden i BPLs, og symbolopløsningen skal matche det.
- Optimering/Inlining har gjort stacktracet sværere at læse.
I praksis betyder det: Til leak-jagten har du brug for et build, der bevidst er „diagnosefähig“. Det er et andet mål end „möglichst schnell“. Tekniske Leads bør håndtere det som et separat build-profil, så ikke hvert teammedlem ad hoc ændrer projektindstillinger.
Frames bewerten: „Interessant“ ist oft eine Zeile weiter oben
Et eksempel fra virkeligheden (uden konkret kundekode): Stacktracet viser dig som første Frame i din kode en rutine „LoadConfig“. Du ser der en Objekt-Erzeugung. Du tilføjer et Free, lækket er væk – og pludselig knaller det et andet sted med Double Free. Hvorfor? Fordi „LoadConfig“ lægger objektet i en Cache, og en anden Codepfad er allerede Owner og rydder op senere.
Den korrekte læsning ville have været: Stacktracet viser dig, hvor blokken opstår. Fixet ligger ofte i definitionen: Hvem ejer objektet efter return? Hvis du ikke besvarer dette spørgsmål klart, skifter du kun fejlbildet (Leak → AV).
Heap-korruption vs. Leak: Hvorfor FullDebugMode ofte identificerer den egentlige årsag
Mange „Leaks“ er i virkeligheden Folgeprobleme: Et Buffer-Overrun overskriver Heap-Metadaten, der Memory-Manager kan senere ikke frigive korrekt, og til sidst ser du tilsyneladende tilfældige Leaks eller Invalid Pointer Operations. FullDebugMode er stærk her, fordi den arbejder med Prüfmustern og ved Free/Reuse udfører ekstra valideringer.
Vigtigt er sondringen:
- Leak: Blokken blev allokeret og aldrig frigivet. Stabilitet forringes over tid; crash er ikke nødvendigvis resultatet.
- Use-after-free: Blokken frigives, men bruges senere stadig. Fører til sporadiske AVs, som er svære at reproducere.
- Double Free: Blokken frigives to gange. Kan fejle med det samme eller først senere (hvis blokken er blevet genbrugt).
- Heap-korruption: Nogen skriver ud over grænserne af en blok. Symptomer er ofte tidsforskudte.
FullDebugMode er særligt værdifuld, når du ser symptomer tidsforskudt. Den ekstra validering gør fejl synlige tidligere – ofte præcis dér, hvor den forkerte adgang sker, ikke først minutter senere i et vilkårligt Free.
Fremgangsmåde i projekter: Reproducerbar Leak-Jagd i stedet for „Debugging im Nebel“
Hvis du vil jage Speicherlecks, har du brug for en proces, der er gentagelig og kan deles i teams. Jeg arbejder gerne med en fast Diagnoserahmen:
1) Reproduktion i et deterministisk scenarium
Fastlæg en testsekvens, som pålideligt viser lækket: „Starte Service, verarbeite 500 Nachrichten, stoppe Service“ eller „Öffne Maske X, führe Aktion Y 200-mal aus“. Det er vigtigt, at du dokumenterer sekvensen med parametre (Datenbestand, Mandant, Feature-Flags), så andre kan reproducere den.
2) Minimieren: Leak pro Schritt sichtbar machen
Hvis sekvensen tager 20 minutter, del den op. Målet er: du vil så hurtigt som muligt kunne sammenligne „vorher“ og „nachher“. I store Anwendungen er det ofte det egentlige Zeitfresser, ikke selve Fixen.
3) FullDebugMode einschalten und Report interpretieren
Nu kommer FastMM FullDebugMode i spil. Saml rapporterne, grupper efter blokstørrelse/callstack og kig efter gentagelser. En enkelt tilbageværende blok kan være en legitim cache. 10.000 identiske blokke er næsten altid et ægte leak.
4) Afklaring af ejerskab og rettelse i det rette lag
Ret leaks dér, hvor ejerskab defineres: Factory, API-kontrakt, Collection-Wrapper. „Sæt hurtigt et Free ind“ lige ved Create er ofte det forkerte sted, hvis objektet sendes videre.
5) Regression: samme sekvens, samme build, samme rapport
Rettelsen er først god, når sekvensen kører igen, og hverken leaks eller nye hukommelsesfejl opstår. Især ved use-after-free er et „leak væk“ ikke bevis, men blot et nyt symptom.
Typiske faldgruber i Delphi-kode, som FastMM gør synlige
Collections og ejerskab (lister, dictionaries, interfaces)
Mange leaks skyldes ikke komplicerede algoritmer, men dagligdagens datastrukturer. To klassiske fejlscenarier:
- En liste indeholder objekter, men ingen ved, hvem der frigør dem. Løsning: brug en owning-liste eller ryd konsekvent i finally.
- Et dictionary holder objekter som values; ved Remove bliver value ikke frigivet eller det glemmes ved Clear.
Derudover er Interfaces ofte problematiske: referencetælling (ARC-lignende) er bekvemt, men blanding med objekt-ejerskab kan ved cykliske referencer eller events skabe leaks. FullDebugMode viser dig ofte allokeringsstien, men årsagen er en referencecyklus (A holder B via interface, B holder A via callback).
Exceptions og tidlige exits
I større, etablerede business-software-systemer er exceptions ofte en del af den normale styring (f.eks. validering, afbrydelse, retry). Problemet er sjældent selve exceptionen, men vejen omkring den: Et objekt oprettes før try/finally, så kastes en exception, og cleanup springes over. FullDebugMode giver dig stacktracen for allokationen – og du skal kontrollere, om der findes en garanteret udført frigivelsessti.
Tråde og levetid: „frigive i den forkerte tråd“
Ved VCL/FMX og services med worker-threads opstår et andet kanttilfælde: Et objekt oprettes i én tråd, men frigives i UI-tråden (eller omvendt), fordi man via Queue/Synchronize „lige hurtigt“ sender noget over. Det kan fungere, men kan også føre til use-after-free, hvis producenten fortsætter arbejdet, mens forbrugeren allerede frigiver.
FastMM FullDebugMode kan hjælpe her, fordi det detekterer tidsforskudte fejl tidligere. Den reelle rettelse er dog en ren levetidsmodel: klare ejerskabsforhold, overførsel kun via immutable data eller entydige ownership-transfer-punkter.
Hvordan du gør rapporter anvendelige: filtrere, sammenligne, dokumentere
I teams er det værd at behandle leak-rapporter ikke kun som noget man „ser på“, men som et artefakt. Tre pragmatiske foranstaltninger, som har vist sig effektive:
- Baseline-Report: En „kendt tilstand“ (f.eks. aktuel produktversion) køres én gang med FullDebugMode og gemmes som reference. Så opdager du nye leaks med det samme.
- Sammenligning efter Use-case: For kritiske workflows (Import, Export, API-Request, UI-Massenoperation) fastlægger du for hver en kort sekvens, der regelmæssigt kan gentages.
- Dokumenterede „legitime Leaks“: Hvis en cache bevidst ikke finaliseres, dokumentér det. Ellers jager en eller anden de samme poster igen om seks måneder.
Det er ikke bureaukrati, men en tidsbesparelse: Jagten på lækager bliver ellers hurtigt en endeløs løkke, fordi de samme mønstre gentager sig i hver sprint.
Hvornår indsatsen er det værd – og hvornår du bør angribe det anderledes
FastMM FullDebugMode er et diagnoseværktøj, der indebærer omkostninger. Indsatsen er især berettiget, når:
- Applikationen kører længe (service, Terminalserver-klient, skiftesystem, 24/7-processer).
- Du behandler reelle kundedatastrømme, og ikke alle veje bliver dækket i test.
- Stabilitet er vigtigere end kortsigtet feature-udviklingshastighed (typisk i procesnære softwareløsninger).
Hvis du derimod kun har et lille desktop-hjælpeprogram, der stopper igen efter 30 sekunder, er jagten på lækager ofte sekundær. Ligeledes: Hvis du har et enkeltstående memory-spike-problem (f.eks. et stort eksport), er det ofte ikke et læk, men et spørgsmål om streaming-strategi og peak-belastning i heapen.
Praksiskonklusion: FullDebugMode er ikke en afbryder, men en proces
FastMM FullDebugMode skaber struktur i hukommelsesfejlsøgningen: Det gør allokationer synlige, opdager heap-korruption tidligere og leverer stacktraces, så du kan rette årsag frem for symptom. Den afgørende løftestang er dog ikke værktøjet, men processen: reproducerbare scenarier, diagnose-egnede builds, klare ejerskabskontrakter og regression imod en baseline.
Hvis du sidder fast i et genstridigt læk eller en sporadisk heap-fejl og ønsker at stabilisere emnet varigt i et større Delphi-system, er et kort, ordentligt diagnose-setup med klar rækkefølge og analyserbare rapporter berettiget. Hvis du har brug for støtte til analyse, build-profiler eller arkitektur-refactoring: Kontakt til Net-Base Software GmbH.
For dette emne er også Delphi Find hukommelseslæk og Fastmm Leak Report-læsning vigtige. Artiklen sætter disse aspekter i en forståelig kontekst og viser, hvad der er vigtigt i dagligdagen.
Næste trin
Når emnet bliver til et reelt projekt, bør arkitektur, eksisterende systemer og drift tidligt vurderes samlet.
Vi støtter ikke kun ved enkeltspørsmål, men også når kildekodeudsnit, legacy-komponenter eller portalidéer skal udvikles til et robust virksomhedsprojekt.
- Eksisterende tilstand, målbillede og tekniske risici vurderes samlet.
- REST, dataadgang, portaler og udrulning bliver ikke udskudt som efterfølgende opgaver.
- De ser tidligt, hvilken vej der er økonomisk og driftsmæssigt bæredygtig.