Net-Base Ajakiri

23.08.2026

Mällelekkide jahipidamine: FastMM FullDebugMode'i sihipärane kasutamine ja stacktrace'ide korrektne lugemine

FastMM FullDebugMode on Delphi-projektides üks tõhusamaid vahendeid mälulekkide vastu – kuid ainult siis, kui see sihipäraselt sisse lülitatakse, aruanded õigesti tõlgendatakse ja tüüpilisi väärarusaamu välditakse. See praktiline juhend näitab selget protsessi alates...

23.08.2026

Ajakirjateemast projektipraktikasse

Sobivad teenuse- ja tehnilised lehed postituse jaoks

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.

Seetõttu on puhas töövoog järgmine: reprodutseeridaisoleeridastacktrace’i lahendadaOwnership-viga leidaparandus koos regressioonitestiga. FastMM annab sulle jäljed, kuid sa pead need tõlkima arhitektuuri ja elutsüklitesse.

FastMM FullDebugMode korrektselt aktiveerida (ilma kõrvalmõjusid eiramata)

Schematische Grafik mit Heap-Blöcken und Prüfrändern, die in einen Leak-Report überführt werden
Kokkuvõte: FullDebugMode töötab täiendavate kontrollservade ja raportiväljundiga.

FullDebugMode aktiveeritakse praktikas FastMM-valikute ja sobiva FastMM-konfiguratsiooniga. Olulisem ei ole niivõrd „kuidas täpselt nimetatakse include-faili“, vaid mida konfiguratsioon põhjustab ja millistel build-tingimustel sa seda kasutad.

Soovitatud tingimused debug-buildi jaoks

  • Debug-DCU-d ja debug-info: Stacktrace’id on kasulikud ainult siis, kui neid saab lahendada tegeliku uniti/rea/aadressini. Veendu, et debug-informatsioon genereeritakse ja et MAP-fail on saadaval.
  • Vali optimeerimine teadlikult: Stacktrace’i loetavuse huvides on tavaliselt parem mitteoptimeeritud build. Inlining ja agressiivne optimeerimine võivad virnastusraame „udustada“.
  • Ühtsed käitustingimused: Kasuta võimaluse korral samu andmeid, sama konfiguratsiooni, samu õigusi. Paljud lekked sõltuvad andmetest (nt harvaesinevad formaadid, eripärased teekonnad).
  • Eralda 64-bit ja 32-bit: Mälu käitumine, joondus ja kolmanda osapoole teegid erinevad. Debuggi sihtplatvormil, kus probleem ilmneb.

Üks punkt, mida adminid ja tehnilised juhid sageli alahindavad: FullDebugMode võib muuta ka ajastust. Kui kasutad lõime, võivad race-condition’id sel viisil teisiti esineda. Seetõttu on mõistlik samal ajal sooritada ka käik ilma FullDebugMode’ita, mis kinnitab ainult reprodutseerimist. FullDebugMode on seejärel samm diagnostikasse.

Ettevaatust 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

Ettevõtterakenduste klassika: importprotsess loob iga kirje kohta abiobjekte (nt StringLists, JSON-Parser, ajutisi loendeid). Happy Path’is vabastatakse need korrektselt. Harvadel juhtudel (valideerimisest tingitud vahelejätmine, Exception, enneaegne Exit) jääb objekt kinni. Pärast 10 000 kirjet muutub see nähtavaks.

FastMM FullDebugMode aitab siin, sest ta näitab allokatsiooni asukohta. Aga „Fix“ ei tähenda „free koht, kus allokatsioon toimus“. Lahendus on robustne Ownership-Pattern:

  • See, kes objekti loob, ei ole automaatselt selle omanik.
  • Ownership peab API-lepingus selgelt määratletud olema (parameeter/tagastusväärtus, dokumentatsioon, nimetamisvõtted).
  • Kogud peavad olema ühemõttelised: owning vs. non-owning. Segavormid maksavad kätte.
  • Erandirajad vajavad varajast try/finally-plokki.

Kui sa stacktrace’ist näed ainult „TStringList.Create“, ei ole see info väärtusetu – aga see ütleb sulle ainult: siin midagi luuakse. Küsimus on: kuhu see peaks lõppema? Siin aitab arhitektuuriline mõtlemine rohkem kui debuggeri-akrobaatika.

Stacktrace’ide korrektne lugemine: mida sellest tegelikult järeldada saab

Detailaufnahme einer Debugging-Analyse mit unscharfem Debugger und handnotierter Call-Chain
Stacktrace’i puhul loeb Call-Chain – mitte üksik rida.

FastMM-i stacktrace on tavaliselt nimekiri tagasipöördumise aadressidest, mis – debug-sümbolite abil – kaardistuvad Units’idele, protseduuridele ja eelistatult ka reanumbrite juurde. Kui sa seda loed, on kolm asja otsustavad:

  • Top-of-Stack ei ole alati viga: ülemised kaadrid on sageli Memory-Manager/RTL. Huvi tekib seal, kus sinu kood algab.
  • Call-Chain, mitte üksik rida: rida on vaid üks punkt. Kett näitab, milline rada jõudis allokatsioonini.
  • Mitmed identsed plokid: kui FastMM teatab mitmest sama suurusega lekkest, on see sageli korduv rada. See on hea: sul on reproduktiivsus.

Kui rea numbreid pole: MAP-Datei, Packages, Release-DCUs

Paljud meeskonnad komistavad siin: FullDebugMode on aktiivne, lekkearuanne tuleb, aga Unit/zeile asemel on ainult aadressid või krüptilised sümbolid. Tüüpilised põhjused:

  • MAP-fail puudub või debug-info ei ole genereeritud.
  • Sa jooksed Release-DCUs või kolmanda osapoole DLL-idega ilma sümboliteta.
  • Rakendus kasutab Runtime Packages: siis paiknevad koodiosad BPL-des ja sümbolite lahendamine peab selle juurde sobima.
  • Optimeerimine/Inlining on stacktrace’i loetavust halvendanud.

Tegelikult tähendab see: lekkide otsimiseks vajad build’i, mis on teadlikult „diagnoosimiseks sobiv“. Selle eesmärk ei ole sama mis „maksimaalselt kiire“. Tehnilised juhid peaksid käsitlema seda eraldi Build-Profiilina, et mitte nõuda iga meeskonnaliikme ad hoc projektisätete muutmist.

Kaadrite hindamine: „Huvitav“ on sageli rida veidi kõrgemal

Reaalsest elust võetud näide (ilma konkreetse kliendi koodita): Stacktrace näitab sulle esimeses kaadris su koodis rutiini „LoadConfig“. Sa näed seal objekti loomist. Lisad Free, leak kaob – ja äkki kukub mõnes teises kohas Double Free-ga. Miks? Sest „LoadConfig“ paneb objekti Cache’i, ja teine koodirada on juba objekti omanik ja koristab hiljem ära.

Õige lugemine oleks olnud: Stacktrace näitab sulle, kus plokk tekib. Parandus peitub sageli määratluses: kes omab objekti pärast Return? Kui sa sellele küsimusele selget vastust ei anna, muutub vaid vigade avaldumine (Leak → AV).

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

Graafik, mis näitab Buffer-Overrunit, mis kirjutab naabermällu
Heap-korruptsioon ilmneb sageli hilinemisega – FullDebugMode muudab selle varem nähtavaks.

Paljud „Leak’id“ on tegelikult järgnevate probleemide tagajärjed: Buffer-Overrun kirjutab üle heap-metadatud, mäluhaldur ei suuda hiljem puhtalt vabastada ja lõpuks näed näiliselt juhuslikke Leake või vigaseid pointerioperatsioone. FullDebugMode on siin tugev, sest ta töötab kontrollmustritega ja teeb Free/Reuse juures täiendavaid valideerimisi.

Oluline on eristamine:

  • Leak: plokk allokeeriti ja ei vabastatud kunagi. Stabiilsus halveneb aja jooksul; krahh ei pruugi tekkida kohe.
  • Use-after-free: plokk vabastatakse, kuid seda kasutatakse hiljem edasi. Põhjustab sporadilisi AV-sid, mida on raske reprodutseerida.
  • Double Free: plokk vabastatakse kaks korda. Võib plahvatada kohe või alles hiljem (kui plokk oli juba uuesti kasutusele võetud).
  • Heap-Korruption: keegi kirjutab ploki piiridest üle. Sümptomid ilmnevad sageli ajaliselt nihkega.

FullDebugMode on eriti väärtuslik, kui sümptomid ilmnevad hilinemisega. Täiendav valideerimine paljastab vead varem – sageli täpselt seal, kus vale ligipääs toimub, mitte alles minutite pärast mõnes suvalises Free’s.

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

Kui tahad mälulekkeid jahtida, vajad protsessi, mis on korduv ja mida saab meeskonnas jagada. Ma töötan eelistatult fikseeritud diagnoosiraamiga:

1) Reproduktion in einem deterministischen Szenario

Määra testijada, mis lekke usaldusväärselt esile toob: „Starte Service, verarbeite 500 Nachrichten, stoppe Service“ või „Öffne Maske X, führe Aktion Y 200-mal aus“. Oluline on dokumenteerida jada koos parameetritega (andmebaas, Mandant, Feature-Flags), et teised saaksid seda järele teha.

2) Minimieren: Leak pro Schritt sichtbar machen

Kui jada kestab 20 minutit, jaga see osadeks. Eesmärk on, et sa saaksid võimalikult kiiresti võrrelda „vorher“ ja „nachher“. Suurtes rakendustes on see sageli tegelik ajakulu, mitte parandamine.

3) FullDebugMode einschalten und Report interpretieren

Nüüd alles tuleb mängu FastMM FullDebugMode. Kogu raportid, grupeerige plokisuuruse/callstacki järgi ja otsige kordusi. Üksik allesjäänud plokk võib olla põhjendatud vahemälu. 10 000 identsset plokki on peaaegu alati tõeline mäluleke.

4) Omandiõiguse selgitamine ja parandamine sobivas kihis

Parandage lekked seal, kus omandiõigus määratakse: Factory, API-leping, Collection-Wrapper. „Kiire Free lisamine“ otse Create kõrvale on sageli vale koht, kui objekti antakse edasi.

5) Regressioon: sama järjend, sama build, sama raport

Parandus on alles hea siis, kui sama järjend töötab uuesti ja ei esine ei lekkimisi ega uusi mäluvigu. Eriti use-after-free puhul ei ole „leke kadunud“ tõend, vaid sageli uus sümptom.

Tüüpilised lõksud Delphi-koodis, mida FastMM nähtavaks teeb

Kogud ja omandiõigus (listid, sõnastikud, liidesed)

Paljud lekked ei tule keerulistest algoritmidest, vaid igapäevastest andmestruktuuridest. Kaks klassikalist veamustrit:

  • Loend sisaldab objekte, kuid keegi ei tea, kes neid vabastab. Lahendus: kasutada owning-loendit või tühjendada järjekindlalt finally-blokis.
  • Sõnastik hoiab objekte väärtustena; Remove puhul ei vabastata väärtust või Clear unustatakse.

Lisaks on liidesed keerulised: viiteloendus (sarnaselt ARC-ile) on mugav, kuid kombinatsioon objekti-omandiõigusega võib tsükliliste viidete või sündmuste puhul tekitada lekkeid. FullDebugMode näitab tihti allokatsiooni rada, kuid põhjus on viitesilmus (A hoiab B-d liidese kaudu, B hoiab A-d callbacki kaudu).

Erandid ja varajased väljumised

Kasvanud äritarkvaras on erandid tihti osa tavalisest juhtimisest (nt valideerimine, katkestamine, korduskatse). Probleem pole tavaliselt erand ise, vaid seda ümbritsev rada: objekt luuakse enne try/finally’d, siis ilmneb erand ja cleanup jäetakse vahele. FullDebugMode annab sulle allokatsiooni stacktrace’i – ja sa pead kontrollima, kas olemas on garanteeritud vabastusrada.

Lõimed ja elutsükkel: „vabastamine vales lõimes“

VCL/FMX ja töötajate lõime kasutavate teenuste puhul tekib veel üks erijuhtum: objekt luuakse ühes lõimes, aga vabastatakse UI-lõimes (või vastupidi), kuna asju edastatakse Queue/Synchronize abil „kiirelt“. See võib töötada, kuid võib viia ka use-after-free’ni, kui tootja (producer) jätkab tööd, samal ajal kui tarbija (consumer) juba vabastab.

FastMM FullDebugMode võib siin abiks olla, sest ta tuvastab ajaviivitusega vead varem. Tegelik parandus on siiski puhas eluea mudel: selged omanikuõigused, üleandmine ainult muutumatute andmete kaudu või ühemõttelised omandiõiguse ülekande punktid.

Kuidas muuta raportid kasutatavaks: filtreerimine, võrdlemine, dokumenteerimine

Meeskondades tasub lekkeraportid mitte ainult vaadata, vaid käsitleda neid kui artefakte. Kolm pragmaatilist meetet, mis on end tõestanud:

  • Baseline-Report: Tuntud seisund (nt praegune tooteversioon) käivitatakse üks kord FullDebugMode’iga ja talletatakse referentsina. Nii tuvastad uued lekked kohe.
  • Võrdlus kasutusjuhtude kaupa: Kritiliste töövoogude (import, eksport, API-päring, UI-massioperatsioon) jaoks määrad igaühele lühikese, regulaarselt kordatava seeria.
  • Dokumenteeritud „legitiimsed lekked“: Kui vahemälu jäetakse teadlikult finaliseerimata, dokumenti see. Muul juhul jookseb keegi kuue kuu pärast jälle sama asja taga.

See ei ole bürokraatia, vaid ajasääst: lekkide jälitamine muutub muidu kiiresti lõputuks ringiks, sest samad mustrid ilmnevad igas sprindis uuesti.

Millal vaev end ära tasub – ja millal peaksid teisiti tegutsema

FastMM FullDebugMode on diagnostikavahend, mille kasutamine nõuab ressursse. Vaev tasub end eriti, kui:

  • Rakendus töötab pikalt (Service, Terminalserver-Client, vahetussüsteem, 24/7-protsessid).
  • Sa töötled reaalset kliendiandmevoogu ja ei kata testides kõiki radu.
  • Stabiilsus on olulisem kui lühiajaline funktsioonide arenduse kiirus (tüüpiline protsessipõhistes tarkvaralahendustes).

Kui sul on seevastu ainult väike töölauaabiprogramm, mis lõpeb pärast 30 sekundit, on lekkide otsimine sageli teisejärguline. Samuti: kui sul on ühekordne mäluhuppel (memory spike) probleem (nt suur eksport), ei ole see sageli lekke küsimus, vaid voogude töötlemise strateegia ja kuhja (heap) tipukoormuse küsimus.

Praktiline järeldus: FullDebugMode ei ole lüliti, vaid protsess

FastMM FullDebugMode toob mäluveaotsingusse struktuuri: see teeb allokatsioonid nähtavaks, avastab heap-korruptsiooni varem ja annab stacktrace’id, mille abil saad parandada põhjust, mitte sümptomit. Otsustav tõstejõud ei ole siiski tööriist, vaid protsess: reprodutseeritavad stsenaariumid, diagnostiliseks sobivad buildid, selged ownership-lepingud ja regressioon baasjoone vastu.

Kui sa jääd kinni kangekaelses lekkes või sporadiliselt esinevas heap-veas ja tahad seda teemat suuremas Delphi-süsteemis püsivalt stabiliseerida, tasub läbi viia lühike, puhas diagnostika-setup selge järjestuse ja analüüsitavate raportitega. Kui vajad selleks tuge analüüsis, build-profiilides või arhitektuuri refaktoreerimises: võta ühendust Net-Base Software GmbH.

Selle teema puhul on olulised ka Delphi mälulekete leidmine ja Fastmm Leak Reporti lugemine. Artikkel paigutab need aspektid arusaadavalt konteksti ja näitab, millele igapäevatöös tähelepanu pöörata.

Arutage projekti või moderniseerimisettevõtmist koos Net-Base.

järgmine samm

Kui teemast saab reaalne projekt, tuleks arhitektuuri, olemasolevat keskkonda ja ekspluatatsiooni varakult koos vaadelda.

Me ei toeta ainult üksikute küsimuste lahendamist, vaid ka siis, kui lähtekoodilõikudest, pärandsüsteemidest või portaalikontseptsioonidest peab saama usaldusväärne ettevõtteprojekt.

  • Olemasolev olukord, sihtpilt ja tehnilised riskid hinnatakse üheskoos.
  • REST, andmejuurdepääs, portaalid ja juurutamine ei lükata hilisemateks tagajärgedeks edasi.
  • Te näete varakult, milline tee on majanduslikult ja operatiivselt jätkusuutlik.

Jaga postitust

Jaga seda postitust otse

LinkedIn, X, XING, Facebook, WhatsApp ja e-post on kohe saadaval. Instagrami jaoks valmistame lingi ja lühiteksti otse ette.

e-post

Instagram avatakse uues vahekaardis. Link ja lühitekst kopeeritakse eelnevalt lõikepuhvrisse.