Från magasinets tema till projektpraxis
Passande tjänste- och tekniksidor för inlägget
Om en Delphi-applikation i drift långsamt „sväller upp“, sporadiskt kraschar med Access Violations eller efter flera dagars körning plötsligt blir instabil, ligger det ofta inte en enstaka bugg bakom utan ett mönster: minne allokeras men frigörs inte ordentligt – eller det frigörs för tidigt och används senare igen. Precis här är FastMM FullDebugMode ovärderligt. Inte som ett permanent tillstånd, utan som ett riktat diagnostikverktyg som förvandlar „någonstans i heapen är något trasigt“ till en spårbar orsak.
Nackdelen: FullDebugMode genererar mycket output, kostar prestanda och leder lätt till felaktiga tolkningar. En Leak-Report visar inte automatiskt den plats där „felet“ är. Och en Stacktrace är bara så bra som symbolupplösningen (MAP-Datei, Debug-Infos, inlining). I det här inlägget går jag igenom det typiska kantfallet, förklarar ett ordnat förhållningssätt och fallgroparna – så att du i slutändan inte bara hittar läckor utan åtgärdar dem varaktigt.
Wann FastMM FullDebugMode wirklich sinnvoll ist
FastMM är i moderna Delphi-versioner ofta redan standardmemorymanager eller integreras i många projekt ändå. FullDebugMode är dock en särskild konfiguration: den förser minnesblock med ytterligare kontrollmönster, samlar allocations-stacktraces och kontrollerar mer aggressivt för heap-korruption (dvs. skadade administrativa data i heapen, t.ex. genom buffer-overruns).
Jag använder FullDebugMode målinriktat när någon av följande situationer föreligger:
- Reproduzierbares Leak: Minnesanvändningen ökar i testkörning per operation (t.ex. per request, per import, per UI-åtgärd).
- Sporadische AVs: Särskilt sådana som inträffar „här och där“ i samma område (klassiskt: use-after-free).
- Heap-Korruption: Meddelanden som „Invalid pointer operation“, „Access violation in ntdll“ eller krascher vid avslut/finalisering.
- Regressionssuche: Efter refaktorisering, bibliotekuppdatering eller kompilatorbyte plötsligt ny instabilitet.
Det är inte meningsfullt att sätta FullDebugMode som „vi slår på det i alla builds“. Overheaden är hög, timingen förändras och särskilt race-conditions kan antingen försvinna eller förskjutas. För kontinuerlig drift passar snarare ett slankt övervakningsupplägg (t.ex. processens working set, Private Bytes, räknare per operation) – FullDebugMode är skalpellen, inte pulsmätaren.
Grundprinzip: Leak-Report ist Symptom, Stacktrace ist Spur
En Leak-Report visar först och främst: Dessa block är fortfarande allokerade vid programmets slut. Det är bara automatiskt ett problem om dessa block egentligen borde ha frigjorts. Det finns legitima „läckor“: globala singletons, caches, OS-handtag med processlivstid eller tredjepartsbibliotek som avsiktligt inte finaliserar. Dessa fall vill du känna till, men inte blint „åtgärda“.
Stacktracen i rapporten visar platsen där blocket allokerades. Det är ofta inte den plats där du „glömt Free“. Vanlig verklighet i växande system:
- Allokering i UI- eller servicelag, frigivning borde ske i ett djupare lager (ownership oklart).
- Allokering i en factory, ownership överlämnas till callern – men callern tror att det är „owned“.
- Objekt hålls i collections (listor, dictionaries), men ownership-modellen är inte konsekvent.
- En undantagsväg hoppar över cleanup eftersom try/finally saknas eller börjar för sent.
Den korrekta arbetsgången är därför: reproducera → isolera → lös upp stacktracen → identifiera ownership-fel → fix med regressionstest. FastMM levererar spåren, men du måste översätta dem till arkitektur och livscykler.
Aktivera FastMM FullDebugMode korrekt (utan att förbise bieffekter)
FullDebugMode aktiveras i praktiken via FastMM-alternativ och en lämplig FastMM-konfiguration. Avgörande är mindre „vad exakt heter include-filen“, och mer vad konfigurationen åstadkommer och under vilka byggförhållanden du använder den.
Rekommenderade ramar för debug-builden
- Debug DCUs och debug‑info: Stacktraces är bara användbara om de kan lösas upp till verklig unit/rad/adress. Säkerställ att debug‑information genereras och att en MAP‑fil finns tillgänglig.
- Välj optimering medvetet: För läsbarheten i stacktraces är en icke‑optimerad build oftast bättre. Inlining och aggressiva optimeringar kan sudda ut stackframes.
- Samma körförhållanden: Använd helst samma data, samma konfiguration, samma rättigheter. Många läckor är data‑beroende (t.ex. sällsynta format, specialvägar).
- Separera 64‑bit och 32‑bit: Minnesbeteende, alignment och tredjepartsl bibliotek skiljer sig åt. Debugga på den målplattform där problemet uppstår.
En punkt som administratörer och tekniska ledare ofta underskattar: FullDebugMode kan också ändra timing. Om trådning är inblandat kan race‑conditions uppträda annorlunda. Därför är det vettigt att parallellt köra ett test utan FullDebugMode som endast bekräftar reproduktionen. FullDebugMode blir då steget för diagnos.
Försiktighet med „ReportMemoryLeaksOnShutdown“
Delphi kan via ReportMemoryLeaksOnShutdown rapportera läckor vid programslut. Det är praktiskt, men i komplexa applikationer (tjänster, Plug-in Host, långa körningar) kan det missleda: vid shutdown körs finaliseringsavsnitt, trådar stoppas, caches töms. En läcka som är kritisk mitt i körningen kan ha försvunnit vid slutet — eller omvänt: en till synes läcka uppstår först vid shutdown eftersom bakgrundsarbete fortfarande pågår.
För praktisk läckagejakt är det därför viktigare att mäta läckor per operation (t.ex. efter 100 requests), inte bara vid avslut. FastMM kan hjälpa till, men testupplägget måste återspegla det.
Det typiska kantfallet: läckagerapporten visar „irgendein Objekt“, men orsaken är Ownership
En klassiker i företagsapplikationer: En importprocess skapar per post hjälrobjekt (t.ex. StringLists, JSON-parser, temporära listor). I det normala fallet frigörs de korrekt. I sällsynta fall (valideringsbetingad skip, exception, tidigt exit) blir ett objekt kvar. Efter 10 000 poster syns det.
FastMM FullDebugMode hjälper här, eftersom det visar allocationsstället. Men „fixen“ är inte att „free på allocationsstället“. Fixen är ett robust ägarskapsmönster:
- Den som skapar ett objekt är inte automatiskt owner.
- Ägarskap måste vara klart i API-kontraktet (parametrar/retur, dokumentation, namngivningskonventioner).
- Collections måste vara entydiga: owning vs. non-owning. Blandformer slår tillbaka.
- Exception‑vägar behöver tidiga try/finally‑block.
Om du i stacktracen bara ser „TStringList.Create“ är informationen inte värdelös – men den säger bara: här skapas något. Frågan är: var bör det sluta? Och där hjälper arkitekturtänk mer än debuggerakrobatik.
Stacktraces korrekt läsa: Vad du verkligen kan dra för slutsatser
En stacktrace från FastMM är i regel en lista med återvändsadresser som – med debug‑symboler – kan mappas till units, procedurer och i bästa fall radnummer. När du läser den är tre saker avgörande:
- Top-of-Stack är inte alltid felet: De översta frames är ofta memory manager/RTL. Intressant blir det där din kod börjar.
- Call‑Chain istället för en enstaka rad: Raden är bara en punkt. Kedjan visar vilken väg som ledde till allokationen.
- Flera identiska block: Om FastMM rapporterar flera läckor av samma storlek är det ofta en återkommande väg. Det är bra: du har reproducerbarhet.
När radnummer saknas: MAP‑fil, Packages, Release‑DCUs
Många team snubblar här: FullDebugMode är aktiv, leak‑rapporten kommer, men istället för unit/rad finns bara adresser eller kryptiska symboler. Typiska orsaker:
- Ingen MAP‑fil eller debug‑info har genererats.
- Du kör mot Release‑DCUs eller tredjeparts‑DLL:er utan symboler.
- Applikationen använder Runtime Packages: då ligger delar av koden i BPLs, och symbolupplösningen måste stämma med det.
- Optimering/inlining har gjort stacktracen svårare att läsa.
I praktiken betyder det: För läckjakt behöver du en build som medvetet är „diagnosbar“. Det är ett annat mål än „så snabbt som möjligt“. Tekniska ledare bör behandla detta som en separat build‑profil, så att inte varje teammedlem ad hoc ändrar projektinställningar.
Värdera frames: „Intressant“ är ofta en rad upp
Ett exempel från verkligheten (utan konkret kundkod): Stacktracen visar dig som första frame i din kod en rutin „LoadConfig“. Du ser där en objektinstansiering. Du lägger till ett Free, leaket försvinner – och plötsligt kraschar det någon annanstans med Double Free. Varför? För att „LoadConfig“ lägger objektet i en cache, och en annan kodväg är redan Owner och städar upp senare.
Den korrekta tolkningen hade varit: Stacktracen visar dig, var blocket uppstår. Åtgärden ligger ofta i definitionen: Vem äger objektet efter return? Om du inte besvarar den frågan tydligt byter du bara felbild (Leak → AV).
Heap-Korruption vs. Leak: Varför FullDebugMode ofta hittar den verkliga boven
Många „Leaks“ är i verkligheten följdproblem: En Buffer-Overrun skriver över heap-metadata, Memory-Manager kan senare inte frigöra minnet korrekt, och i slutändan ser du till synes slumpmässiga Leaks eller Invalid Pointer Operations. FullDebugMode är här effektiv eftersom den arbetar med kontrollmönster och vid Free/Reuse gör extra valideringar.
Viktigt är åtskillnaden:
- Leak: Ett block allokerades och frigjordes aldrig. Stabiliteten försämras över tid, krasch är inte nödvändigtvis följden.
- Use-after-free: Blocket frigörs men används senare fortfarande. Leder till sporadiska AVs som är svåra att reproducera.
- Double Free: Blocket frigörs två gånger. Kan krascha omedelbart eller först senare (när blocket återanvänds).
- Heap-Korruption: Någon skriver över gränserna för ett block. Symtomen är ofta fördröjda.
FullDebugMode är särskilt värdefullt när du ser symtom fördröjt. Den extra valideringen gör fel synliga tidigare – ofta exakt där det felaktiga åtkomsten sker, inte först minuter senare i ett godtyckligt Free.
Arbetsgång i projekt: Reproducerbar Leak-jakt istället för „Debugging im Nebel”
Om du vill jaga minnesläckor behöver du ett förlopp som är upprepbart och kan delas inom teamet. Jag arbetar gärna med en fast diagnosticeringsram:
1) Reproduktion i ett deterministiskt scenario
Definiera en testsekvens som visar leaket på ett pålitligt sätt: „Starta service, bearbeta 500 meddelanden, stoppa service“ eller „Öppna form X, utför åtgärd Y 200 gånger“. Viktigt är att du dokumenterar sekvensen med parametrar (datamängd, klient, feature-flaggor) så att andra kan återskapa den.
2) Minimera: gör leak synligt per steg
Om sekvensen tar 20 minuter, dela upp den. Målet är att du så snabbt som möjligt vill jämföra „före“ och „efter“. I stora applikationer är det ofta detta som tar mest tid, inte själva åtgärdandet.
3) Sätt på FullDebugMode och tolka rapporten
Först nu kommer FastMM FullDebugMode in i bilden. Samla rapporterna, gruppera efter blockstorlek/callstack och leta efter upprepningar. En enstaka kvarvarande block kan vara en legitim cache. 10.000 identiska block är nästan alltid en verklig läcka.
4) Ägandeskap: klarläggning och åtgärd i rätt lager
Åtgärda läckor där ägandeskapet definieras: Factory, API-kontrakt, Collection-Wrapper. „Snabbt lägga in en Free“ direkt intill Create är ofta fel plats om objektet förs vidare.
5) Regression: samma sekvens, samma build, samma rapport
Åtgärden är först bra när sekvensen körs igen och varken läckor eller nya minnesfel uppträder. Speciellt vid Use-after-free är ett „läcka borta“ inget bevis, utan bara ett nytt symptom.
Typiska fallgropar i Delphi-kod som FastMM gör synliga
Collections och ägandeskap (listor, Dictionaries, gränssnitt)
Många läckor kommer inte från komplicerade algoritmer utan från vardagliga datastrukturer. Två klassiska felbilder:
- En lista innehåller objekt, men ingen vet vem som frigör dem. Lösning: använd en ägande lista eller töm konsekvent i finally.
- Ett Dictionary håller objekt som Values; vid Remove frigörs inte Value eller glöms bort vid Clear.
Dessutom är gränssnitt knepiga: referensräkning (ARC-liknande) är bekvämt, men blandning med objektägandeskap kan vid cykliska referenser eller events skapa läckor. FullDebugMode visar då ofta allokeringsvägen, men orsaken är en referenscykel (A håller B via interface, B håller A via callback).
Exceptions och tidiga exits
I etablerade affärssystem är Exceptions ofta en del av normal styrning (t.ex. validering, avbrott, retry). Problemet är sällan själva exceptionen utan vägen runt den: Ett objekt skapas före try/finally, sedan kastas en exception och cleanup hoppas över. FullDebugMode ger dig stacktracen för allokeringen – och du måste kontrollera om det finns en garanterat exekverad frigöringsväg.
Trådar och livstid: „frigöra i fel tråd“
I VCL/FMX och tjänster med worker-threads uppstår ett ytterligare hörnfall: Ett objekt skapas i en tråd men frigörs i UI-tråden (eller tvärtom) eftersom man via Queue/Synchronize „bara snabbt“ för över något. Det kan fungera, men kan också leda till Use-after-free om producenten fortsätter arbeta medan konsumenten redan frigör.
FastMM FullDebugMode kan hjälpa här eftersom det upptäcker tidsfördröjda fel tidigare. Den faktiska åtgärden är dock en ren livstidsmodell: tydliga ägandeförhållanden, överlämning endast via immutabla data eller entydiga ownership-överföringspunkter.
Hur du gör rapporter användbara: filtrera, jämföra, dokumentera
I team är det värt att inte bara „titta på“ leak-rapporter utan behandla dem som ett artefakt. Tre pragmatiska åtgärder som visat sig fungera:
- Baseline-Report: Ett „känt tillstånd“ (t.ex. aktuell produktversion) körs en gång med FullDebugMode och sparas som referens. Då upptäcker du nya läckor omedelbart.
- Jämförelse efter Use-Case: För kritiska arbetsflöden (Import, Export, API-Request, UI-massoperation) fastställer du för varje en kort sekvens som regelbundet kan upprepas.
- Dokumenterade „legitima läckor“: Om en cache medvetet inte finaliseras, dokumentera det. Annars jagar någon samma poster igen om sex månader.
Det här är ingen byråkrati utan en tidsbesparing: jakt på läckor blir annars snabbt en ändlös loop eftersom samma mönster återkommer i varje sprint.
När insatsen lönar sig – och när du bör agera annorlunda
FastMM FullDebugMode är ett diagnosverktyg som innebär en kostnad. Insatsen lönar sig särskilt när:
- Applikationen körs länge (service, Terminalserver-Client, skiftsystem, 24/7-processer).
- Du bearbetar verkliga kunddataflöden och inte täcker alla kodvägar i testerna.
- Stabilitet är viktigare än kortsiktig leveranstakt för nya funktioner (typiskt i processnära mjukvarulösningar).
Om du däremot bara har ett litet desktop-verktyg som avslutas efter 30 sekunder är läckjakt ofta sekundär. Likaså: om du har ett engångsproblem med ett minnespuckel (t.ex. stor export) är det ofta inte ett läckage utan en fråga om streamingstrategi och toppbelastning i heapen.
Praktisk slutsats: FullDebugMode är ingen strömbrytare utan en process
FastMM FullDebugMode ger struktur åt felsökning av minnesfel: det gör allokeringar synliga, upptäcker heap-korruption tidigare och levererar stacktraces som låter dig åtgärda orsaken istället för symptomet. Den avgörande hävstången är dock inte verktyget i sig utan processen: reproducerbara scenarier, diagnostiska builds, tydliga ägarskapsavtal och regressionstest mot en baslinje.
Om du sitter fast i ett seglivat läckage eller sporadiska heap-fel och vill stabilisera detta i ett större Delphi-system på lång sikt, lönar sig en kort, välordnad diagnosuppsättning med klar sekvens och utvärderbara rapporter. Behöver du stöd med analys, build-profiler eller arkitekturrefaktorisering: kontakta Net-Base Software GmbH.
För detta ämne är även Delphi Hitta minnesläckor och Läsa Fastmm Leak Report viktiga. Artikeln placerar dessa aspekter begripligt och visar vad som är avgörande i praktiken.
nästa steg
När ett ämne blir ett verkligt projekt bör arkitektur, befintligt bestånd och drift tidigt ses över gemensamt.
Vi stöder inte bara vid enstaka frågor, utan även när kodsfragment, legacy-frågor eller portalidéer ska utvecklas till ett robust företagsprojekt.
- Nuläge, målbild och tekniska risker bedöms tillsammans.
- REST, dataåtkomst, portaler och utrullning skjuts inte upp som sena följder.
- Ni ser tidigt vilken väg som är ekonomiskt och driftmässigt hållbar.