Net-Base Žurnalas

23.08.2026

Atminties nutekėjimų paieška: FastMM FullDebugMode tikslingas naudojimas ir stack trace'ų teisingas skaitymas.

FastMM FullDebugMode yra vienas iš efektyviausių įrankių kovai su atminties nutekėjimais Delphi-projektuose – tačiau tik tuo atveju, jei jis tikslingai įjungiamas, ataskaitos teisingai interpretuojamos ir vengiama tipinių klaidingų prielaidų. Šis praktinis straipsnis parodo aiškų procesą nuo...

23.08.2026

Nuo žurnalo temos iki projekto įgyvendinimo

Tinkami puslapiai apie paslaugas ir techninę informaciją šiam įrašui

Jei Delphi-taikomoji programa veikloje palaipsniui „išsipučia“, periodiškai baigiasi Access Violations arba po kelių dienų veikimo staiga tampa nestabili, dažnai nėra kalta viena klaida, o pasikartojantis modelis: atmintis užklausiama, bet nevalingai neįlaisvinama – arba atlaisvinama per anksti ir vėliau dar naudojama. Būtent čia FastMM FullDebugMode suteikia didelę diagnostinę naudą. Ne kaip nuolatinė būsena, o kaip taiklus diagnostikos įrankis, paverčiantis „kur nors heap’e kažkas negerai“ į atsekamą priežastį.

Trūkumas: FullDebugMode generuoja daug išvesties, sumažina našumą ir greitai gali vesti į klaidingas interpretacijas. Atminties nutekėjimo ataskaita (Leak-Report) neparodo automatiškai vietos, kur iš tiesų yra „klaida“. O stacktrace yra vertingas tik tiek, kiek gera simbolių išsprendimo kokybė (MAP-failas, debug-informacija, inlining). Šiame įraše aptarsiu tipinį kraštinį atvejį, paaiškinsiu tvarkingą prieigą ir spąstus – taip, kad tu pabaigoje ne tik rastum nutekėjimus, bet ir nuosekliai juos pašalintum.

Kada FastMM FullDebugMode iš tiesų prasmingas

FastMM moderniose Delphi-versijose dažnai jau yra numatytasis atminties valdiklis arba daugelyje projektų jis taip ir taip yra įtrauktas. Tačiau FullDebugMode yra ypatinga konfigūracija: jis pažymi atminties blokus papildomais kontroliniais žymenimis, renka alokacijų stacktrace’us ir agresyviau tikrina heap-korupciją (t. y. sugadintas valdymo struktūras heap’e, pvz., dėl buffer-overrun).

Aš taikau FullDebugMode tikslingai, kai pasireiškia vienas iš šių scenarijų:

  • Reprodukuojamas nutekėjimas: atminties naudojimas testavimo metu auga už kiekvieną operaciją (pvz. už request, importą arba UI veiksmą).
  • Periodiški AVs: ypač tie, kurie „kartais čia, kartais ten“ vyksta tame pačiame regione (klasikinis atvejis: Use-after-free).
  • Heap-Korruption: pranešimai kaip „Invalid pointer operation“, „Access violation in ntdll“ arba gedimai uždarant/finalizuojant programą.
  • Regresijų paieška: po refaktoringo, bibliotekos atnaujinimo ar kompiliatoriaus keitimo staiga pasirodo naujų nestabilumų.

FullDebugMode nėra prasmingas kaip „įjunkime tai visuose build’uose“. Režimas sukelia didelį overhead, keičia laiko parametrus (timing) ir dėl to būtent race-condition’ai gali išnykti arba pasislinkti. Ilgalaikiam eksploatavimui labiau tinka lengvas monitoringas (pvz., proceso Working-Set, Private Bytes, skaitikliai už vieną operaciją) – FullDebugMode yra skalpelis, ne pulso jutiklis.

Grundprinzip: Leak-Report ist Symptom, Stacktrace ist Spur

Atminties nutekėjimo ataskaita pirmiausia parodo: Šie blokai programos pabaigoje vis dar alokuoti. Tai yra problema tik tada, jei šiuos blokus iš tiesų reikėjo išlaisvinti. Yra teisėtų „leak’ų“: globalūs Singletons, talpyklos, OS-handeliai su proceso gyvavimo trukme arba trečiųjų šalių bibliotekos, kurios tyčia neatlieka finalizacijos. Šias situacijas tu turi žinoti, bet ne aklai „ištaisyti“.

Stacktrace ataskaitoje nurodo vietą, kur blokas buvo užklaustas. Tai dažnai nėra vieta, kur tu „pamiršai Free“. Dažna realybė augančiuose sistemose:

  • Allokacija UI arba service sluoksnyje, išlaisvinimas turėtų vykti žemesniame sluoksnyje (savininkystė neaiški).
  • Allokacija fabrike, savininkystė perduodama caller’ui – bet caller mano, kad jis yra „owned“.
  • Objektai laikomi kolekcijose (sąrašai, Dictionaries), tačiau savininkystės modelis nėra nuoseklus.
  • Išimties (Exception) kelias praleidžia valymą, nes trūksta try/finally arba jis pradedamas per vėlai.

Todėl nuosekli eiga yra: atkurtiizoliuotiišanalizuoti Stacktracerasti nuosavybės klaidątaisymas su regresijos testu. FastMM pateikia pėdsakus, tačiau juos reikia perteikti architektūros ir gyvenimo ciklų kontekste.

FastMM FullDebugMode teisingas aktyvavimas (nepraleidžiant šalutinių poveikių)

Schematische Grafik mit Heap-Blöcken und Prüfrändern, die in einen Leak-Report überführt werden
Santrauka: FullDebugMode naudoja papildomus patikros rėmus ir ataskaitų išvestį.

FullDebugMode praktikoje įjungiamas per FastMM parinktis ir tinkamą FastMM konfigūraciją. Svarbiau ne tiek „kaip tiksliai vadinasi include failas“, kiek ką konfigūracija lemia ir kokiomis sudarymo sąlygomis ją naudojate.

Rekomenduojamos sąlygos derinimo (Debug) sudarymui

  • Debug DCUs ir Debug-informacija: Stacktrace’ai naudingi tik tada, kai juos galima atsekti iki tikros Unit/eilutės/adreso. Įsitikinkite, kad sugeneruojama debug-informacija ir yra MAP failas.
  • Optimizaciją sąmoningai pasirinkti: Stacktrace skaitomumui dažniausiai geriau naudoti neoptimizuotą sudarymą. Kodo įterpimas (inlining) ir agresyvus optimizavimas gali „išplauti“ Stackframe’us.
  • Tos pačios vykdymo sąlygos: Naudokite kuo panašesnius duomenis, tą pačią konfigūraciją, tas pačias teises. Daugelis nutekėjimų priklauso nuo duomenų (pvz., reti formatai, specialūs keliai).
  • Atskirkite 64-bit ir 32-bit: Atminties elgsena, išlygiavimas ir trečiųjų šalių bibliotekos skiriasi. Derinkite toje tikslinėje platformoje, kurioje problema pasireiškia.

Viena sritis, kurią dažnai nuvertina adminai ir techniniai vadovai: FullDebugMode gali taip pat pakeisti vykdymo laiko elgseną. Jei naudojate daugiagijį vykdymą, dėl to lenktynių sąlygos gali pasireikšti kitaip. Todėl prasminga lygiagrečiai turėti ir paleidimą be FullDebugMode, kuris tik patvirtina reprodukciją. FullDebugMode tuomet yra diagnostikos žingsnis.

Atsargiai su „ReportMemoryLeaksOnShutdown“

Delphi gali per ReportMemoryLeaksOnShutdown pranešti apie nutekėjimus programos pabaigoje. Tai patogu, bet sudėtingose programose (servisai, Plug-in Host, ilgos veikimo trukmės) tai gali klaidinti: išsijungimo metu vykdomi finalizacijos blokai, gijos stabdomos, talpyklos išvalomos. Nutekėjimas, kuris vykdymo viduryje yra kritiškas, gali iki pabaigos išnykti – arba atvirkščiai: tariamas nutekėjimas atsiranda tik išsijungimo metu, nes vis dar vyksta foniniai darbai.

Praktiškai tikslingam nutekėjimų paieškai svarbiau: matuoti nuotėkį per operaciją (pvz., po 100 užklausų), o ne tik programos pabaigoje. FastMM gali padėti, bet testų aplinka turi tai atspindėti.

Tipinis kraštutinis atvejis: nuotėkio ataskaita rodo „bet kokį objektą“, tačiau priežastis yra nuosavybės valdymas

Klasika verslo programose: importo procesas kiekvienam įrašui sukuria pagalbinius objektus (pvz. StringLists, JSON-Parser, laikinas sąrašas). Įprastiniu atveju jie tinkamai atlaisvinami. Retais atvejais (validacijos praleidimas, Exception, ankstyvas išeitimas) objektas lieka „pakibęs“. Po 10 000 įrašų tai tampa matoma.

FastMM FullDebugMode čia padeda, nes jis parodo alokacijos vietą. Tačiau „sprendimas“ nėra tiesiog „free to the place of allocation“. Sprendimas yra tvirtas nuosavybės modelis:

  • Tas, kas sukuria objektą, automatiškai nėra jo savininkas.
  • Nuosavybė turi būti aiški API sutartyje (parametrai/grąžinimas, dokumentacija, vardų konvencijos).
  • Kolekcijos turi būti vienareikšmės: owning vs. non-owning. Mišrios formos atsigręžia prieš tave.
  • Išimčių keliai reikalauja ankstyvų try/finally blokų.

Jei iš stacktrace matote tik „TStringList.Create“, informacija nėra beverčė – bet ji sako tik: čia kažkas sukuriama. Klausimas yra: kur tai turėtų baigtis? Architektūrinis mąstymas čia padeda labiau nei debuggerio akrobatika.

Stacktrace’ų teisingas skaitymas: ką iš jų tikrai galima išvesti

Detailaufnahme einer Debugging-Analyse mit unscharfem Debugger und handnotierter Call-Chain
Stacktrace’e svarbi kvietimų grandinė – ne pavienė eilutė.

FastMM generuojamas stacktrace paprastai yra atgalinių perdavimų adresų sąrašas, kurie – su debug simboliais – atvaizduojami į Units, procedūras ir, idealu atveju, į eilučių numerius. Skaitant tai, trys dalykai yra lemiami:

  • Stako viršūnė ne visada yra klaidos vieta: viršutiniai rėmai dažnai priklauso Memory-Manager/RTL. Įdomu tampa ten, kur prasideda jūsų kodas.
  • Kvietimų grandinė, o ne viena eilutė: eilutė yra tik taškas. Grandinė parodo, koks kelias vedė prie alokacijos.
  • Keli identiški blokai: jei FastMM praneša apie kelis vienodo dydžio atminties nutekėjimus, tai dažnai reiškia pasikartojantį kelią. Tai gerai: turite reprodukuojamumą.

Jei trūksta eilučių numerių: MAP-Datei, Packages, Release-DCUs

Daugelis komandų čia užstringa: FullDebugMode įjungtas, leak ataskaita gaunama, bet vietoje Unit/ eilutės matote tik adresus arba kriptines žymes. Tipinės priežastys:

  • Nėra MAP-failo arba nebuvo sugeneruota debug-informacija.
  • Veikiate su Release-DCUs arba trečiųjų šalių DLL, neturinčių simbolių.
  • Programa naudoja Runtime Packages: tada dalys kodo yra BPL failuose, ir simbolių atvaizdavimas turi tam atitikti.
  • Optimizacija/inlining padarė stacktrace mažiau įskaitomu.

Praktikoje tai reiškia: atminties nutekėjimų paieškai jums reikia build’o, kuris sąmoningai yra „diagnostinis“. Tai kitas tikslas nei „kaip įmanoma greičiau“. Techniniai vadovai turėtų tai tvarkyti kaip atskirą build profilį, kad kiekvienas komandos narys nebūtų priverstas ad hoc keisti projekto parinkčių.

Rėmų vertinimas: „Įdomu“ dažnai yra viena eilutė aukščiau

Reali situacija (be konkretaus kliento kodo): Stacktrace pirmajame tavo kodo frame rodo rutiną „LoadConfig“. Ten matyti objekto sukūrimas. Pridėjus Free, derinimas rodo, kad Leak dingsta – ir staiga kitur vyksta Double Free. Kodėl? Nes „LoadConfig“ deda objektą į cache, o kitas kodo kelias jau yra Owner ir vėliau jį išvalo.

Teisingas skaitymas būtų buvęs: Stacktrace nurodo tau, kur atsiranda blokas. Fix dažnai slypi apibrėžime: Kas yra objekto savininkas po Return? Jei šio klausimo aiškiai neapibrėši, tu tik pakeisi klaidos simptomus (Leak → AV).

Heap-Korruption vs. Leak: Kodėl FullDebugMode dažnai aptinka tikrąjį kaltininką

Grafikas, rodantis buffer-overrun, kuris perrašo gretimą atminties sritį
Heap-Korruption dažnai pasireiškia su laiko atidėjimu – FullDebugMode leidžia ją pastebėti anksčiau.

Daugelis „Leaks“ iš tiesų yra pasekminės problemos: buffer-overrun perrašo heap-metaduomenis, memory-manager vėliau negali tvarkingai atlaisvinti bloko, ir galiausiai matai tariamus atsitiktinius Leaks arba Invalid Pointer Operations. FullDebugMode čia yra ypač naudingas, nes jis naudoja tikrinimo modelius ir vykdant Free/Reuse atlieka papildomas validacijas.

Svarbu atskirti:

  • Leak: Blokas buvo allokuotas ir niekada nebuvo išlaisvintas. Stabilumas blogėja per laiką; crash nėra būtinas.
  • Use-after-free: Blokas išlaisvinamas, bet vėliau dar naudojamas. Sukelia sporadinius AVs, kuriuos sunku atkartoti.
  • Double Free: Blokas išlaisvinamas du kartus. Gali sugriūti iš karto arba tik vėliau (kai blokas buvo pakartotinai panaudotas).
  • Heap-Korruption: Kas nors rašo už bloko ribų. Simptomai dažnai pasirodo su laiko nuokrypiu.

FullDebugMode yra ypač vertingas, kai simptomai pasireiškia su laiko nuokrypiu. Papildoma validacija padaro klaidas matomas anksčiau – dažnai tiesiogiai toje vietoje, kur įvyksta neteisingas prieigos veiksmas, o ne tik po kelių minučių atsitiktiniame Free.

Veiksmų seka projektuose: reproducijojama Leak-medžioklė vietoje „debugginimo migloje“

Jei nori medžioti atminties nutekėjimus, reikalingas procesas, kuris būtų pakartojamas ir perduodamas komandai. Aš dažnai dirbu su fiksuotu diagnozės rėmu:

1) Reprodukcija deterministiniame scenarijuje

Nustatyk testinę seką, kuri patikimai parodo Leak: „Starte Service, verarbeite 500 Nachrichten, stoppe Service“ arba „Öffne Maske X, führe Aktion Y 200-mal aus“. Svarbu dokumentuoti seką su parametrais (duomenų rinkinys, klientas/mandantas, Feature-Flags), kad kiti galėtų ją atkartoti.

2) Minimieren: Leak pro Schritt sichtbar machen

Jei seka trunka 20 minučių, padalink ją. Tikslas: nori kuo greičiau palyginti „prieš“ ir „po“. Didelėse aplikacijose tai dažnai yra pagrindinis laiko sąnaudų šaltinis, ne pats klaidos taisymas.

3) FullDebugMode einschalten und Report interpretieren

Dabar įsijungia FastMM FullDebugMode. Surink ataskaitas, sugrupuok pagal bloko dydį ir callstack ir ieškok pasikartojimų. Vienas likęs blokas gali būti teisėta talpykla. 10 000 identiškų blokų beveik visada reiškia tikrą atminties nutekėjimą.

4) Ownership aiškinimas ir pataisa tinkamame sluoksnyje

Ištaisyk nutekėjimus ten, kur apibrėžiama Ownership: Factory, API kontraktas, Collection-Wrapper. „Greitai įterpti Free“ tiesiai šalia Create dažnai yra neteisinga vieta, jei objektas perduodamas toliau.

5) Regresija: ta pati seka, tas pats build, ta pati ataskaita

Pataisa gerai veikia tik tada, kai seka vėl vykdoma ir nei nutekėjimų, nei naujų atminties klaidų nepasireiškia. Ypač Use-after-free atveju „Leak weg“ nėra įrodymas, o tik naujas simptomas.

Tipiški spąstai Delphi kode, kuriuos mato FastMM

Kolekcijos ir Ownership (sąrašai, žodynai, interfeisai)

Daugelis nutekėjimų kyla ne iš sudėtingų algoritmų, o iš kasdienių duomenų struktūrų. Du klasikiniai klaidų modeliai:

  • Sąrašas talpina objektus, bet niekas nežino, kas juos atlaisvina. Sprendimas: naudoti owning sąrašą arba nuosekliai išvalyti finally bloke.
  • Dictionary laiko objektus kaip values; Remove metu value nėra atlaisvinamas arba Clear metu pamirštama.

Papildomai sudėtingi yra interfeisai: referencijų skaičiavimas (ARC-ähnlich) yra patogu, bet mišrus režimas su objektų Ownership gali sukelti nutekėjimų ciklinių nuorodų ar įvykių atveju. FullDebugMode dažnai parodo alokacijos kelią, tačiau priežastis būna referencijų ciklas (A laiko B per Interface, B laiko A per Callback).

Išimtys ir ankstyvi išėjimai

Išaugusiose verslo programinės įrangos sistemose išimtys dažnai yra normalaus valdymo dalis (pvz., validacija, nutraukimas, retry). Problema retai būna pati išimtis; dažniau — aplinkinis kelias: objektas sukuriamas prieš try/finally, tada kyla išimtis ir cleanup praleidžiamas. FullDebugMode pateikia alokacijos stacktrace — ir reikia patikrinti, ar egzistuoja garantuotas išlaisvinimo kelias.

Gijos ir gyvavimo trukmė: „išlaisvinimas netinkamoje gijoje“

Su VCL/FMX ir servisais su worker-threads atsiranda dar viena kampinė situacija: objektas sukuriamas vienoje gijoje, bet UI-gijoje išlaisvinamas (ar atvirkščiai), nes per Queue/Synchronize „greitai“ kažką perduodama. Tai gali veikti, bet taip pat gali sukelti Use-after-free, jei producer toliau dirba, o consumer jau atlaisvina.

FastMM FullDebugMode čia gali padėti, nes jis anksčiau aptinka laiko vėlinamus klaidų atvejus. Tačiau tikrasis pataisymas yra tvarkingas gyvavimo laiko modelis: aiškios nuosavybės teisės, perdavimas tik per nekintamus (immutable) duomenis arba aiškūs Ownership perdavimo taškai.

Kaip padaryti ataskaitas naudingas: filtravimas, palyginimas, dokumentavimas

Komandoms verta leak-reports ne tik „peržiūrėti“, bet traktuoti kaip artefaktą. Trys pragmatiški veiksmai, kurie pasiteisino:

  • Baseline-Report: „žinomas būsena“ (pvz., esama produkto versija) vieną kartą paleidžiama su FullDebugMode ir saugoma kaip referencija. Tada nauji nutekėjimai matomi iškart.
  • Paliginimas pagal Use-Case: kritiniams workflow’ams (Import, Export, API-Request, UI masinės operacijos) apibrėžiate trumpą, reguliariai pakartojamą seką.
  • Dokumentuoti „teisėti nutekėjimai“: jei talpykla sąmoningai nėra finalizuojama, užfiksuokite tai dokumentacijoje. Priešingu atveju po šešių mėnesių vėl kažkas medžios tuos pačius įrašus.

Tai nėra biurokratija, o laiko taupymas: nuotėkių paieška kitu atveju greitai virsta begaline kilpa, nes tie patys modeliai kartojasi kiekviename sprinte.

Kada pastangos atsiperka – ir kada turėtum elgtis kitaip

FastMM FullDebugMode yra diagnostikos įrankis, susijęs su papildomomis sąnaudomis. Pastangos ypač atsiperka, kai:

  • Programa ilgai veikia (Service, Terminalserver-Client, pamainų sistema, 24/7 procesai).
  • Tu apdoroji realius klientų duomenų srautus ir testais nepadengi visų vykdymo kelių.
  • Stabilumas svarbesnis už trumpalaikį leidimo tempą naujiems funkcionalumams (įprasta procesu artimose programinėse sistemose).

Jei priešingai turi tik mažą darbalaukio pagalbinę programėlę, kuri baigiasi po 30 sekundžių, nuotėkių paieška dažnai yra antraeilis uždavinys. Taip pat: jei susiduri su vienkartiniu atminties šuoliu (pvz., didelis eksportas), dažnai tai nėra nuotėkis, o klausimas apie srautų apdorojimo strategiją ir heap’o piko apkrovą.

Praktinė išvada: FullDebugMode nėra jungiklis, o procesas

FastMM FullDebugMode įveda struktūrą atminties klaidų paieškai: jis padaro alokacijas matomas, ankstyviau atskleidžia heap korupciją ir pateikia stack trace’us, su kuriais gali taisyti priežastį, o ne simptomą. Tačiau lemiamas svertas nėra įrankis, o eiga: reprodukuojami scenarijai, diagnostikai tinkami build’ai, aiškūs atsakomybės susitarimai ir regresija, lyginama su bazine versija.

Jei įstringai su užsispyrusiu nuotėkiu arba sporadine heap klaida ir nori šią sritį tvariai stabilizuoti didesnėje Delphi sistemoje, apsimoka trumpas, tvarkingas diagnostikos nustatymas su aiškia seka ir analizuojamomis ataskaitomis. Jei reikia pagalbos analizėje, build profiliuose ar architektūros refaktoringe: susisiek su Net-Base Software GmbH.

Šiai temai taip pat svarbūs Delphi Speicherleck Finden ir Fastmm Leak Report skaitymas. Straipsnis aiškiai išdėsto šiuos aspektus ir parodo, kas svarbu kasdieniame darbe.

Aptarti projektą arba modernizacijos užmojus su Net-Base.

Sekantis žingsnis

Kai iš temos tampa realus projektas, architektūrą, esamą aplinką ir eksploatavimą reikėtų anksti nagrinėti kartu.

Mes padedame ne tik pavienėse užklausose, bet ir tuomet, kai iš šaltinio kodo fragmentų, paveldėtų temų ar portalo idėjų turi tapti patikimas įmonės projektas.

  • Esama padėtis, tikslinis vaizdas ir techninės rizikos vertinami kartu.
  • REST, duomenų prieiga, portalai ir diegimas nebus atidedami į vėlesnes stadijas.
  • Jūs anksti matote, kuris kelias yra ekonomiškai ir įmonės veiklos požiūriu tvarus.

Pasidalinti įrašu

Tiesiogiai pasidalinti šiuo įrašu

LinkedIn, X, XING, Facebook, WhatsApp ir el. paštas yra iš karto prieinami. Instagramui parengiame nuorodą ir trumpą tekstą nedelsiant.

El. paštas

Instagram atidaromas naujame skirtuke. Nuoroda ir trumpas tekstas iš anksto nukopijuojami į iškarpinę.