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). В тази статия разглеждам типичния ръбов случай, обяснявам чистия подход и подводните камъни – така че накрая да не само намериш leak-ове, а да ги премахнеш трайно.

Кога FastMM FullDebugMode е наистина целесъобразен

FastMM в съвременните версии на Delphi често вече е default-Memory-Manager или е включен в много проекти. FullDebugMode обаче е специална конфигурация: той маркира блоковете памет с допълнителни проверъчни шаблони, събира Allokations-Stacktraces и проверява по-агресивно за корупция на heap-а (т.е. повредени управляващи данни в heap-а, напр. вследствие на Buffer-Overruns).

Използвам FullDebugMode целенасочено, когато е налице една от следните картини:

  • Възпроизведим Leak: потреблението на памет расте в тестовия пробег на операция (напр. на всеки Request, при всеки Import, при всяко UI-действие).
  • Спорадични AVs: особено такива, които „понякога тук, понякога там“ се случват в една и съща област (класически: Use-after-free).
  • Корупция на Heap-а: съобщения като „Invalid pointer operation“, „Access violation in ntdll“ или сривове при затваряне/файнализация.
  • Търсене на регресия: след рефакторинг, ъпдейт на библиотека или смяна на компилатор внезапно се появява нова нестабилност.

Не е разумно да се включва FullDebugMode като „да го пуснем във всички билдове“. Оувърхедът е голям, таймингът се променя и именно race-conditions могат да изчезнат или да се изместят. За постоянна експлоатация по-подходящ е лек мониторинг (напр. процесно Working-Set, Private Bytes, брояч на операция) – FullDebugMode е скалпелът, не пулсният сензор.

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

Един Leak-Report първо ти показва: Тези блокове са все още алокирани при края на програмата. Това е автоматично проблем само ако тези блокове всъщност е трябвало да бъдат освободени. Има легитимни „leak-ове“: глобални Singleton-и, кешове, OS-Handles с lifetime на процеса или third-party библиотеки, които нарочно не финализират. Тези случаи трябва да ги познаваш, но не да ги „поправяш“ на сляпо.

Stacktrace-ът в отчета показва мястото, където блокът е бил алокиран. Това често не е мястото, където си „забравил да Free-неш“. Често срещаната реалност в пораснали системи:

  • Алокация в UI- или service-слой, освобождаването трябва да се случи в по-дълбок слой (ownership неясно).
  • Алокация във фабрика, ownership се предава на caller-а – но caller-ът мисли, че то е „owned“.
  • Обекти се държат в колекции (Listen, Dictionaries), но ownership-моделът не е консистентен.
  • Път при Exception пропуска почистване, защото try/finally липсва или започва твърде късно.

Поради това правилният процес е: възпроизвежданеизолиранеразрешаване на stacktraceнамиране на грешки в Ownershipпоправка с регресионен тест. FastMM ти дава следите, но ти трябва да ги преведеш в архитектура и жизнени цикли.

FastMM FullDebugMode sauber aktivieren (ohne Nebenwirkungen zu übersehen)

Схематична графика с хип-блокове и защитни ръбове, които се прехвърлят в отчет за изтичане на памет
Резюме: FullDebugMode работи с допълнителни защитни ръбове и генериране на отчет.

FullDebugMode се активира на практика чрез опциите на FastMM и подходяща FastMM-конфигурация. По-важно е не толкова „как точно се казва include-файлът“, колкото какво прави конфигурацията и при кои условия на компилация я използваш.

Empfohlene Rahmenbedingungen für den Debug-Build

  • Debug DCUs und Debug-Infos: Stacktraces са полезни само ако могат да се разрешат до реална Unit/ред/адрес. Увери се, че се генерира Debug-информация и че е наличен MAP-файл.
  • Optimierung bewusst wählen: За четимост на stacktrace обикновено е по-добър не-оптимизиран билд. Inlining и агресивните оптимизации могат да „размият“ стек фреймове.
  • Gleiche Laufzeitbedingungen: Използвай възможно най-същите данни, същата конфигурация, същите права. Много течове зависят от данните (напр. редки формати, специални пътеки).
  • 64-bit vs. 32-bit trennen: Поведението на паметта, подравняването и трети библиотеки се различават. Дебъгвай на целевата платформа, където възниква проблемът.

Един аспект, който администраторите и техническите лидери често подценяват: FullDebugMode може да промени и тайминга. Ако имаш многонитово изпълнение, състезателните условия могат да се проявят различно. Затова е разумно паралелно да имаш и изпълнение без FullDebugMode, което само потвърждава възпроизвеждането. FullDebugMode е тогава стъпката за диагностика.

Vorsicht bei „ReportMemoryLeaksOnShutdown“

Delphi може чрез ReportMemoryLeaksOnShutdown да докладва течове при приключване на програмата. Това е удобно, но при сложни приложения (Services, Plug-in Host, дълги времена на работа) може да подвежда: при shutdown се изпълняват финализационни участъци, нишките спират, кешовете се изчистват. Теч, който е критичен по средата на изпълнението, може до края да изчезне – или обратното: привиден теч се появява само при shutdown, защото все още върви фонова работа.

За практическо търсене на течове е по-важно: измерване на теч на операция (напр. след 100 заявки), а не само при приключване. FastMM може да помогне, но тестовата постановка трябва да го отразява.

Der typische Randfall: Leak-Report zeigt „irgendein Objekt“, aber die Ursache ist Ownership

Класика в корпоративните приложения: процес на импортиране създава за всеки запис спомагателни обекти (напр. StringLists, JSON-парсъри, временни списъци). В нормалния път се освобождават коректно. В редки случаи (пропуск поради валидация, Exception, ранно прекъсване) обект остава висящ. След 10.000 записа това става видимо.

FastMM FullDebugMode помага тук, защото показва мястото на алокацията. Но „Fix“-ът не е „free на мястото на алокацията“. Fix-ът е едно робустно Ownership-Pattern:

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

Ако от stacktrace виждаш само „TStringList.Create“, информацията не е безполезна – но тя ти казва само: тук се създава нещо. Въпросът е: къде трябва да приключи? И тук архитектурното мислене помага повече от дебъгърска акробатика.

Как да четеш stacktraces правилно: какво всъщност можеш да извлечеш от тях

Макро снимка на анализ при дебъг с размазан дебъгер и ръчно отбелязана верига на извиквания
При stacktrace брои веригата на извикванията (Call-Chain) – не отделният ред.

Един stacktrace от FastMM обикновено е списък с адреси на връщане, които – с Debug-символи – се картографират на Units, Prozeduren и идеално номера на редове. Когато го четеш, три неща са съществени:

  • Top-of-Stack не винаги е грешката: Най-горните фреймове често са Memory-Manager/RTL. Интересното започва там, където е твоят код.
  • Call-Chain вместо един ред: Редът е само точка. Веригата ти показва по кой път е довела алокацията.
  • Няколко еднакви блока: Ако FastMM отчете няколко leak-а със същия размер, това често е повтарящ се път. Това е добре: имаш възпроизводимост.

Ако липсват номера на редове: MAP-Datei, Packages, Release-DCUs

Много екипи се спъват тук: FullDebugMode е активен, Leak-Report идва, но вместо Unit/Zeile има само адреси или криптични символи. Типични причини:

  • Няма MAP-Datei или Debug-информациите не са генерирани.
  • Стартираш срещу Release-DCUs или Dritt-DLLs без символи.
  • Приложението използва Runtime Packages: тогава части от кода са в BPLs и резолюцията на символите трябва да съвпада.
  • Оптимизация/Inlining направиха stacktrace-а по-труден за четене.

На практика това означава: За лов на leak-ове имаш нужда от билд, който е съзнателно „diagnosefähig“. Това е различна цел от „възможно най-бързо“. Техническите Leads трябва да третират това като отделен Build-Profil, за да не променя всеки член на екипа ad hoc опциите на проекта.

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

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

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

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

Графика, която показва препълване на буфер, което преминава в съседен участък от паметта
Heap-Korruption често се проявява със закъснение – FullDebugMode я прави видима по-рано.

Много „Leaks“ всъщност са последващи проблеми: препълване на буфер (Buffer-Overrun) презаписва heap-метаданните, memory-мениджърът по-късно не може да освободи чисто и в крайна сметка виждаш на пръв поглед произволни Leaks или невалидни операции с указатели. FullDebugMode е силен в тези случаи, защото работи с контролни шаблони и при Free/Reuse прави допълнителни валидации.

Важно е разграничението:

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

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

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

Ако искаш да търсиш изтичания на паметта, ти е нужен процес, който е повторяем и може да се споделя в екипа. Аз предпочитам да работя с фиксирана диагностична рамка:

1) Reproduktion in einem deterministischen Szenario

Определи тестова последователност, която надеждно показва Leak-а: „Стартирай услугата, обработи 500 съобщения, спри услугата“ или „Отвори маска X, изпълни действие Y 200 пъти“. Важно е да документираш последователността с параметри (набор от данни, мандант, feature-флагове), за да могат други да я възпроизведат.

2) Minimieren: Leak pro Schritt sichtbar machen

Ако последователността отнема 20 минути, раздели я. Целта е: да можеш възможно най-бързо да сравняваш „преди“ и „след“. В големи приложения това често е основният консуматор на време, не самото фиксиране.

3) FullDebugMode einschalten und Report interpretieren

Сега влиза в игра 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“ direkt neben Create ist oft der falsche Ort, wenn das Objekt weitergereicht wird.

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)

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

  • Един списък съдържа обекти, но никой не знае кой ги освобождава. Lösung: owning Liste verwenden oder konsequent im finally räumen.
  • Ein Dictionary hält Objekte als Values; beim Remove wird der Value nicht freigegeben oder beim Clear vergessen.

Освен това проблематични са Interfaces: Referenzzählung (ARC-ähnlich) е удобна, но Mischbetrieb mit Objekt-Ownership може при циклични референции или събития да предизвика утечки. FullDebugMode показва често пътя на алокацията, но причината е референтен цикъл (A hält B über Interface, B hält A über Callback).

Exceptions und frühe Exits

В развити бизнес-софтуерни системи са възможни ситуации, в които Exceptions са част от нормалния контрол (z. B. Validierung, Abbruch, Retry). Проблемът рядко е самата Exception, а пътят около нея: Ein Objekt wird vor dem try/finally erzeugt, dann fliegt eine Exception, und der Cleanup wird übersprungen. FullDebugMode liefert dir den Stacktrace der Allokation – und du musst prüfen, ob es einen garantiert ausgeführten Freigabepfad gibt.

Threads und Lebenszeit: „Freigeben im falschen Thread“

При VCL/FMX и услуги с Worker-Threads възниква още един граничен случай: Ein Objekt wird in einem Thread erzeugt, aber im UI-Thread freigegeben (oder umgekehrt), weil man per Queue/Synchronize „nur schnell“ etwas rüberreicht. Това може да работи, но може и да доведе до use-after-free, ако Producer продължава да работи, докато Consumer вече е освободил.

FastMM FullDebugMode може да помогне тук, защото детектира zeitversetzte Fehler по-рано. Истинското решение обаче е чист модел на жизнения цикъл: ясни Besitzverhältnisse, Übergabe nur über immutable Daten или eindeutige Ownership-Transfer-Punkte.

Wie du Reports nutzbar machst: Filtern, vergleichen, dokumentieren

В екипи има смисъл Leak-Reports да не се „аншауен“ само, а да се третират като артефакт. Три прагматични мерки, die sich bewährt haben:

  • Baseline-Report: Ein „bekannter Zustand“ (z. B. aktuelle Produktversion) wird einmal mit FullDebugMode durchlaufen und als Referenz abgelegt. Dann erkennst du neue Leaks sofort.
  • Vergleich nach Use-Case: Für kritische Workflows (Import, Export, API-Request, UI-Massenoperation) legst du jeweils eine kurze Sequenz fest, die regelmäßig wiederholbar ist.
  • Dokumentierte „legitime Leaks“: Wenn ein Cache bewusst nicht finalisiert, dokumentiere das. Sonst jagt in sechs Monaten jemand wieder die gleichen Einträge.

Това не е бюрокрация, а спестяване на време: иначе ловът на изтичания бързо се превръща в безкраен цикъл, защото същите модели се повтарят в всеки спринт.

Кога усилието си струва – и кога трябва да подходиш по различен начин

FastMM FullDebugMode е диагностичен инструмент, който носи разходи. Усилието си струва особено когато:

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

Ако обаче имаш само малък десктоп помощник, който приключва след 30 секунди, ловът на изтичания често е второстепенен. Също така: ако имаш еднократен спайк в паметта (напр. голям експорт), това често не е изтичане, а въпрос на стратегия за стрийминг и пиково натоварване в heap-а.

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

FastMM FullDebugMode внася структура в търсенето на грешки в паметта: той прави алокациите видими, открива корупция на heap-а по-рано и предоставя stacktraces, с които можеш да отстраниш причината, а не симптома. Решаващият лост обаче не е инструментът, а процесът: възпроизводими сценарии, билдове годни за диагностика, ясни договорености за ownership и регресия спрямо базова линия.

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

За тази тема са важни също Delphi Намиране на изтичания на памет и Fastmm Leak Report Lesen. Статията поставя тези аспекти в разбираем контекст и показва на какво да обръщаш внимание в ежедневната практика.

Обсъди проект или модернизационно начинание с Net-Base.

Следваща стъпка

Когато темата прерасне в реален проект, архитектурата, съществуващите активи и експлоатацията трябва да се разглеждат заедно още в ранния етап.

Подпомагаме не само при отделни въпроси, но и когато от фрагменти от изходен код, проблеми с наследени системи или идеи за портал трябва да бъде реализиран надежден корпоративен проект.

  • Сегашното състояние, целевото състояние и техническите рискове се оценяват съвместно.
  • REST, достъпът до данни, порталите и разгръщането не се отлагат като по-късни последващи задачи.
  • Вие виждате навреме кой път е икономически и оперативно жизнеспособен.

Сподели публикацията

Споделете тази публикация директно

LinkedIn, X, XING, Facebook, WhatsApp и електронна поща са налични веднага. За Instagram подготвяме директно връзка и кратък текст.

Електронна поща

Instagram се отваря в нов раздел. Връзката и краткият текст се копират предварително в клипборда.