Net-Base списание

23.08.2026

Гонење на протекување на меморија: целно користење на FastMM FullDebugMode и правилно читање на Stacktraces

FastMM FullDebugMode е едно од најострите алатки против протекување на меморијата во Delphi-проектите – но само ако се вклучи намерно, извештаите се толкуваат правилно и се избегнуваат типичните погрешни претпоставки. Овој практичен прилог покажува јасен тек од...

23.08.2026

Од тема во магазинот до проектна пракса

Соодветни страници за услуги и технички информации поврзани со објавата

Кога една Delphi-апликација во производство бавно „се надува“, повремено паѓа со Access Violations или по неколку дена работа одеднаш станува нестабилна, зад тоа често не стои еден единствен баг, туку образец: меморијата се бара, но не се ослободува чисто — или се ослободува предвреме и подоцна се користи повторно. Токму тука е FastMM FullDebugMode од голема вредност. Не како постојана состојба, туку како насочен дијагностички алат што од „некде во heap-от нешто е скршено“ прави повторливо и разбирливо потекло на проблемот.

Проблемот е: FullDebugMode произведува голем излез, ја намалува перформансата и брзо води кон погрешни толкувања. Leak-Report не покажува автоматски местото каде што „е грешката“. А Stacktrace е добар само колку што е резолуцијата на симболите (MAP-Datei, Debug-Infos, inlining). Во овој напис ќе го разгледам типичниот рабен случај, ќе го објаснам чистиот пристап и стапките за сопнување — така што на крајот нема само да најдеш Leaks, туку и трајно да ги отстраниш.

Кога FastMM FullDebugMode навистина има смисла

FastMM во современите верзии на Delphi често веќе е подразбираниот Memory-Manager или во многу проекти веќе е вклучен. Но FullDebugMode е посебна конфигурација: тој ги опременува блоковите на меморија со дополнителни проверувачки обрасци, собира Allocation-Stacktraces и поагресивно проверува за Heap-Korruption (т.е. оштетување на административните податоци во heap-от, на пр. поради Buffer-Overruns).

Го користам FullDebugMode насочено кога е присутна една од следниве слики:

  • Reproduzierbares Leak: потрошувачката на меморија се зголемува во тест-вртење по операција (на пр. по Request, по Import, по UI-акција).
  • Sporadische AVs: особено оние што „понекогаш тука, понекогаш таму“ во иста област се појавуваат (класично: Use-after-free).
  • Heap-Korruption: пораки како „Invalid pointer operation“, „Access violation in ntdll“ или падови при затворање/финализирање.
  • Regressionssuche: по рефакторинг, ажурирање на библиотека или промена на компајлерот одеднаш се појавува нова нестабилност.

Не е соодветно да се користи FullDebugMode како „да го вклучиме во сите билдови“. Overhead-от е голем, времеските услови се менуваат, и токму Race-Conditions може да исчезнат или да се преместат. За постојан оперативен режим попогодно е тенко мониторирање (на пр. Prozess-Working-Set, Private Bytes, броачи по операција) – FullDebugMode е скалпел, не сензор за пулс.

Основен принцип: Leak-Report е симптом, Stacktrace е трага

Еден Leak-Report прво ти покажува: Овие блокови сè уште се алоцирани при завршување на програмата. Тоа е проблем само ако тие блокови всушност требало да бидат ослободени. Постојат легитимни „Leaks“: глобални Singletons, кешеви, OS-Handles со животен век колку процесот или third-party библиотеки кои намерно не се финализираат. Тие случаи треба да ги познаваш, но не да ги „wegfixen“ на слепо.

Stacktrace-от во извештајот го покажува местото каде блокот е побаран. Тоа често не е местото каде што си „заборавил да повикаш Free“. Честа реалност во развиени системи:

  • Алокација во UI- или Service-слојот, ослободувањето треба да се случи во подлабок слој (Ownership не е јасен).
  • Алокација во фабрика, Ownership се префрла на Caller-от – но Caller-от мисли дека тоа е „owned“.
  • Објекти се држат во колекции (Listи, Dictionaries), но моделот на Ownership не е конзистентен.
  • Патека за Exception прескокнува Cleanup, бидејќи try/finally недостига или започнува предоцна.

Поради тоа, чистиот тек е: репродуцирањеизолирањеразрешување на Stacktraceнаоѓање на Ownership-грешкификс со регресионски тест. FastMM ти ги дава траговите, но мораш да ги пресликеш во архитектура и животни циклуси.

FastMM FullDebugMode чисто активирање (без да се занемарат нусефектите)

Шематска графика со heap-блокови и проверувачки граници што се пренесуваат во Leak-Report
Апстракт: FullDebugMode работи со дополнителни проверувачки граници и генерирање на извештај.

FullDebugMode се активира во пракса преку опциите на FastMM и со соодветна FastMM-конфигурација. Помалку е пресудно „колку точно се вика Include-датотеката“, а поважно е што конфигурацијата предизвикува и под кои услови на билд ја користиш.

Препорачани услови за Debug-билд

  • Debug DCUs und Debug-Infos: Stacktraces се корисни само ако може да се разрешат на вистински Unit/линија/адреса. Осигурај се дека се генерираат debug-информации и дека MAP-датотека е достапна.
  • Свестен избор на оптимизација: За читливост на Stacktrace вообичаено е подобро да имаш не-оптимизиран билд. Inlining и агресивна оптимизација може да ги „заматат“ стек-фрејмовите.
  • Исти услови при извршување: Користи што е можно исти податоци, иста конфигурација, исти права. Многу leaks зависат од податоците (на пр., редки формати, посебни патеки).
  • Оддели 64-bit и 32-bit: Поведението на меморијата, порамнувањето и трети библиотеки се различни. Дебагирај на целната платформа на која се појавува проблемот.

Една точка што администраторите и техничките лидери често ја потценуваат: FullDebugMode може да го измени и времето на извршување. Ако имаш Threading во игра, Race-Conditions може да се појават поинаку. Затоа е разумно паралелно да извршиш и еден циклус без FullDebugMode кој само ја потврдува репродукцијата. FullDebugMode е тогаш чекорот за дијагноза.

Внимание кај „ReportMemoryLeaksOnShutdown“

Delphi може преку ReportMemoryLeaksOnShutdown да пријави leaks при завршување на програмата. Тоа е практично, но во комплексни апликации (услуги, Plug-in Host, долги времиња на работа) може да биде лажно: при shutdown се извршуваат финализациони делови, нишките се запираат, кешевите се исчистени. Leak што е критичен во средината на извршувањето може да исчезне до крајот — или обратното: наводен leak се појавува дури при shutdown затоа што сè уште има позадинска работа.

За практично Leak-Hunting е поважно: мерат leak по операција (на пр. по 100 requests), а не само при завршување. FastMM може да помогне при тоа, но тест-склопот мора да го прикаже тоа.

Типичниот маргинален случај: Leak-Report покажува „некој објект“, но причината е Ownership

Класичен проблем во корпоративните апликации: еден процес за увоз создава по запис помошни објекти (на пр. StringLists, JSON-Parser, привремени листи). Во успешниот пат (Happy Path) тие се ослободуваат правилно. Во ретки случаи (поради валидација прескок, Exception, рано Exit) некој објект останува заглавен. По 10.000 записи тоа станува видливо.

FastMM FullDebugMode помага тука, бидејќи покажува каде е алокацијата. Но „фиксовото“ решение не е „free на местото на алокацијата“. Решението е едно робустно Ownership-Pattern:

  • Кој создава објект, не е автоматски негов Owner.
  • Ownership мора да биде јасно дефинирано во API-договорот (параметри/Return, документација, конвенции за именување).
  • Колекциите мора да бидат недвосмислени: owning vs. non-owning. Мешани форми се одмаздуваат.
  • Патеките со Exception бараат рани try/finally блокови.

Ако од stacktrace видиш само „TStringList.Create“, таа информација не е бесполезна – но ти кажува само: тука се создава нешто. Прашањето е: каде треба тоа да заврши? И тука размислувањето на архитектура помага повеќе отколку акробациите со дебагерот.

Како правилно да се читаат Stacktraces: што навистина можеш да извлечеш

Detailaufnahme einer Debugging-Analyse mit unscharfem Debugger und handnotierter Call-Chain
Кај stacktrace брои call-chain‑от – не поединечниот ред.

Stacktrace од FastMM обично е листа на адреси за враќање, кои – со debug-симболи – се мапираат на Units, процедури и, идеално, број на линии. Кога го читаш тоа, три работи се пресудни:

  • Top-of-Stack не е секогаш грешката: Горните фрејмови често се Memory-Manager/RTL. Интересно станува таму каде што почнува твојот код.
  • Call-Chain наместо поединечен ред: Редот е само еден момент. Низата ти покажува кој пат доведе до алокацијата.
  • Повеќе идентични блокови: Ако FastMM пријавува повеќе leak‑ови со иста големина, тоа често е повторлив пат. Тоа е добро: имаш репродуцибилност.

Кога недостасуваат бројки на линии: MAP-Datei, Packages, Release-DCUs

Многу тимови се сопнуваат тука: FullDebugMode е активен, доаѓа leak-репорт, но наместо Unit/линија има само адреси или криптични симболи. Типични причини:

  • Нема MAP-датотека или не се генерирани debug-информации.
  • Се користат Release-DCUs или трети DLL-и без симболи.
  • Апликацијата користи Runtime Packages: тогаш делови од кодот се во BPLs и резолуцијата на симболите треба да биде соодветна.
  • Оптимизацијата/Inlining го направиле stacktrace‑от потешко читлив.

Во пракса тоа значи: за лов на leak-ови ти треба билд што е намерно „diagnosefähig“. Тоа е различна цел од „возможно најбрзо“. Техничките лидери треба да го третираат тоа како посебен Build‑профил, за да не се менуваат проектните опции ad hoc од секој член на тимот.

Оцена на фрејмовите: „Интересно“ често е еден ред погоре

Пример од реалниот свет (без конкретен код на клиент): Stacktrace ти го покажува како првиот фрејм во твојот код рутина „LoadConfig“. Таму гледаш создавање на објект. Додадеш повик на Free, Leak-от исчезнува – и одеднаш пука на друго место со Double Free. Зошто? Затоа што „LoadConfig“ го поставува објектот во кеш, а друг патека на код веќе е Owner и ќе расчисти подоцна.

Правилното читање би било: Stacktrace ти покажува каде настанува блокот. Поправката често лежи во дефиницијата: Кој е сопственик на објектот по повратокот? Ако ова прашање не го одговориш јасно, само го менуваш обликот на грешката (Leak → AV).

Heap-Korruption vs. Leak: Зошто FullDebugMode често ја открива вистинската причина

Графика што покажува Buffer-Overrun кој прелева во соседен простор во меморијата
Heap-Korruption често се манифестира со задоцнување – FullDebugMode ја прави видлива порано.

Многу „Leaks“ во вистина се секундарни проблеми: Buffer-Overrun ја презапишува Heap-Metadaten, Memory-Manager-от подоцна не може чисто да ослободи, и на крајот гледаш наизглед случајни Leaks или Invalid Pointer Operations. FullDebugMode е моќен тука, затоа што работи со контролни патерни и при Free/Reuse врши дополнителни валидации.

Важно е разликувањето:

  • Leak: Блокот е алокиран и никогаш не е ослободен. Стабилноста страда со текот на времето, краш не е нужно.
  • Use-after-free: Блокот е ослободен, но подоцна се користи. Води до спорадични AVs кои се тешко репродуцирачки.
  • Double Free: Блокот се ослободува двапати. Може да предизвика моментален пад или да се јави подоцна (кога блокот е повторно искористен).
  • Heap-Korruption: Некоја друга компонента пишува преку границите на блокот. Симптомите често се појавуваат со задоцнување.

FullDebugMode е особено вреден кога симптомите се појавуваат со задоцнување. Дополнителната валидација ги прави грешките видливи порано – често точно на местото каде што се случува погрешниот пристап, а не минути подоцна во некој произволен Free.

Пристап во проектите: Репродуктивно пребарување на Leak-ови наместо „debugging во магла“

Ако сакаш да ги ловиш мемориските исфрлања, ти треба процес кој е повторлив и што може да се сподели во тимот. Јас работам со фиксна рамка за дијагностика:

1) Репродукција во детерминистички сценарио

Определи тест-секвенца која сигурно го репродуцира leak-от: „Стартувај сервис, обработи 500 пораки, запри сервис“ или „Отвори форма X, изврши акција Y 200 пати“. Важно е да ја документираш секвенцата со параметри (состојба на податоци, мандант, Feature-Flags), за другите да можат да ја воспостават.

2) Минимизирање: Направи leak видлив по секој чекор

Ако секвенцата трае 20 минути, раздели ја. Целта е: да можеш што побрзо да споредиш „пред“ и „потоа“. Во големи апликации тоа често е вистинскиот трошеч на време, не самото отклонување.

3) Вклучи FullDebugMode и интерпретирај го репортот

Сега влегува во игра FastMM FullDebugMode. Собери ги извештаите, групирај по големина на блок/callstack и провери за повторувања. Еден преостанат блок може да е легитимен кеш. 10.000 идентични блокови се речиси секогаш вистинско истекување.

4) Ownership-Klärung und Fix in der passenden Schicht

Поправувај ги истекувањата таму каде што е дефинирана Ownership: Factory, API-Vertrag, Collection-Wrapper. „Schnell ein Free einbauen“ директно покрај Create често е погрешно место ако објектот се предава понатаму.

5) Regression: gleiche Sequenz, gleicher Build, gleicher Report

Поправката е добра само кога секвенцата повторно ќе се извршува и нема да се појават ниту истекувања ниту нови грешки со меморијата. Особено кај Use-after-free, „Leak weg“ не е доказ, туку само нов симптом.

Typische Fallstricke in Delphi-Code, die FastMM sichtbar macht

Collections und Ownership (Listen, Dictionaries, Interfaces)

Многу истекувања не произлегуваат од комплицирани алгоритми, туку од секојдневни структури на податоци. Два класични случаја на грешки:

  • Листа содржи објекти, но никој не знае кој ги ослободува. Решение: користете owning листа или доследно исчистете во finally.
  • Речникот држи објекти како Values; при Remove вредноста не се ослободува или се заборава при Clear.

Дополнително проблематични се Interfaces: броењето на референции (слично на ARC) е практично, но мешањето со објект-Ownership може да создаде истекувања при циклични референци или настани. FullDebugMode често ќе ти покаже патеката на алокација, но причината е референцилен циклус (A држи B преку Interface, B држи A преку Callback).

Exceptions und frühe Exits

Во зрели бизнис-софтерски системи Exceptions често се дел од нормалната контрола (на пр. валидација, прекин, retry). Проблемот ретко е самата Exception, туку патеката околу неа: објектот е создаден пред try/finally, потоа се фрла Exception и cleanup-от се прескокнува. FullDebugMode ти дава stacktrace на алокацијата – и мораш да провериш дали постои гарантиран пат за ослободување.

Threads und Lebenszeit: „Freigeben im falschen Thread“

Кај VCL/FMX и сервиси со worker-threads се појавува уште еден граничен случај: објектот е создаден во еден thread, но е ослободен во UI-Thread (или обратно), бидејќи преку Queue/Synchronize се пренесува „само брзо“ нешто. Тоа може да работи, но може да доведе до Use-after-free ако Producer продолжи да работи додека Consumer веќе ослободува.

FastMM FullDebugMode може да помогне тука, бидејќи открива временски поместени грешки порано. Сепак, вистинската поправка е чист модел на животен век: јасни односи на сопственост, предавање само преку immutable податоци или јасни точки за Ownership-Transfer.

Wie du Reports nutzbar machst: Filtern, vergleichen, dokumentieren

Во тимови вреди Leak-Reports да не се само „погледнат“, туку да се третираат како артефакт. Три прагматични мерки што се покажале како ефективни:

  • Baseline-Report: Една „позната состојба“ (на пр. актуелна верзија на продуктот) се извршува еднаш со FullDebugMode и се чува како референца. Така веднаш ги препознаваш новите истекувања.
  • Vergleich nach Use-Case: За критични workflows (Import, Export, API-Request, UI-Massenoperation) дефинираш кратка секвенца за секој која може редовно да се повторува.
  • Dokumentierte „legitime Leaks“: Ако кеш намерно не се финализира, документирај го тоа. Инаку по шест месеци некој повторно ќе ги гонат истите записи.

Ова не е бирократија, туку заштеда на време: ловот на истекување на меморија инаку брзо станува бесконечна петља, бидејќи истите шаблони се појавуваат повторно во секој спринт.

Кога вложениот напор се исплатува – и кога треба да постапиш поинаку

FastMM FullDebugMode е дијагностичка алатка што носи трошоци. Вложениот напор се исплатува особено кога:

  • Апликацијата работи долги периоди (услуга, клиент на терминален сервер, сменски систем, 24/7-процеси).
  • Обработуваш реални текови на клиентски податоци и не ги покриваш сите патеки во тестовете.
  • Стабилноста е поважна од краткорочната брзина на испорака на функции (типично за софтверни решенија блиски до процесот).

Ако, пак, имаш само мал десктоп-алат што завршува по 30 секунди, ловот на истекувања често е секундарен. Исто така: ако се соочуваш со еднократно „memory spike“ (на пр. голем экспорт), тоа често не е истекување туку прашање на стратегијата за стриминг и пик-оптоварување во heap.

Практичен заклучок: FullDebugMode не е прекинувач, туку процес

FastMM FullDebugMode воведува структура во пребарувањето на грешки во меморијата: ги прави алокациите видливи, рано открива корупција во heap и обезбедува стек-трејсови со кои можеш да ја поправиш причината наместо симптомот. Клучниот лост не е само алатката, туку целиот тек: репродуцибилни сценарија, билдови погодни за дијагностика, чисти договори за сопственост и регресно тестирање во однос на базична линија.

Ако заглавуваш со упорно истекување или спорадични грешки во heap и сакаш да ја стабилизираш темата одржливо во поголем Delphi-систем, исплати се кратко, чисто дијагностичко подесување со јасна секвенца и извештаи погодни за анализа. Ако ти треба поддршка при анализа, профили на билдови или архитектонски рефакторинг: контакт со Net-Base Software GmbH.

За оваа тема се важни и Delphi пронаоѓање на протекување на меморија и читање на Fastmm Leak Report. Текстот ги става овие аспекти во разбирачки контекст и покажува на што се фокусира секојдневната работа.

Разговарај за проект или модернизациски потфат со Net-Base.

Следен чекор

Кога од темата ќе стане реален проект, архитектурата, постојниот систем и експлоатацијата треба рано да се разгледаат заедно.

Не поддржуваме само при поединечни прашања, туку и кога од исечоци од изворен код, legacy-теми или идеи за портали треба да прерасне во робустен корпоративен проект.

  • Постоечката состојба, целната слика и техничките ризици се проценуваат заедно.
  • REST, пристапот до податоци, порталите и Rollout не се одложуваат за подоцнежна фаза.
  • Ќе увидите рано кој пат е економски и оперативно одржлив.

Сподели објава

Споделете го овој пост директно.

LinkedIn, X, XING, Facebook, WhatsApp и е-пошта се достапни веднаш. За Instagram подготвуваме линк и краток текст.

Е-пошта

Instagram се отвора во нов таб. Линкот и краткиот текст претходно се копираат во меѓуспремникот.