Dergi konusundan proje pratiğine
İçeriğe Uygun Hizmet ve Teknik Sayfalar
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.
Bu nedenle düzgün akış şu şekildedir: tekrar üretme → izole etme → stack trace çözme → Ownership hatasını bulma → regresyon testi ile düzeltme. FastMM sana izleri sağlar, ancak bunları mimariye ve yaşam döngülerine çevirmen gerekir.
FastMM FullDebugMode’u düzgün etkinleştirme (yan etkileri gözden kaçırmadan)
FullDebugMode pratikte FastMM seçenekleri ve uygun bir FastMM yapılandırmasıyla etkinleştirilir. Belirleyici olan, „include dosyasının tam adı nedir“ değil; yapılandırmanın ne yaptığı ve hangi derleme koşullarında onu kullandığınızdır.
Debug derlemesi için önerilen çerçeve koşulları
- Debug DCU’ları ve hata ayıklama bilgileri: Stack trace’ler yalnızca gerçek birim/satır/adrese çözülebiliyorsa yararlıdır. Hata ayıklama bilgilerinin oluşturulduğundan ve bir MAP dosyasının mevcut olduğundan emin olun.
- Optimizasyonu bilinçli seçin: Stack trace okunabilirliği için genellikle optimize edilmemiş bir derleme daha iyidir. Inlining ve agresif optimizasyon stack frame’lerini „bulanıklaştırabilir“.
- Aynı çalışma zamanı koşulları: Mümkünse aynı verileri, aynı yapılandırmayı, aynı yetkileri kullanın. Birçok sızıntı veri bağımlıdır (ör. nadir formatlar, özel yollar).
- 64-bit ve 32-bit ayrımı: Bellek davranışı, hizalama ve üçüncü taraf kütüphaneler farklılık gösterir. Sorunun oluştuğu hedef platformda hata ayıklayın.
Bir nokta ki sistem yöneticileri ve teknik liderler sıkça hafife alır: FullDebugMode zamanlamayı değiştirebilir. Threading söz konusu olduğunda yarış durumları farklı biçimde ortaya çıkabilir. Bu yüzden paralel olarak FullDebugMode olmadan da, yalnızca yeniden üretimi doğrulayan bir çalışma yapmak mantıklıdır. FullDebugMode ise tanı koyma adımıdır.
„ReportMemoryLeaksOnShutdown“ konusunda dikkat
Delphi ReportMemoryLeaksOnShutdown aracılığıyla program sonlanırken sızıntıları raporlayabilir. Bu pratik olmakla birlikte, karmaşık uygulamalarda (servisler, eklenti hostu, uzun çalışma süreleri) yanıltıcı olabilir: Kapanış sırasında finalization bölümleri çalışır, thread’ler durur, önbellekler temizlenir. Çalışma zamanının ortasında kritik olan bir sızıntı sona kadar kaybolabilir — veya tersine: görünen bir sızıntı ancak kapanışta ortaya çıkabilir çünkü arka planda hâlâ işler sürüyordur.
Pratikte işe yarayan sızıntı avı için bu nedenle daha önemli olan: işlem başına sızıntıyı ölçmek (ör. 100 isteğin ardından), sadece sonlandırmada değil. FastMM bu konuda yardımcı olabilir, ancak test düzeni bunu yansıtacak şekilde kurulmalıdır.
Tipik kenar durum: Sızıntı raporu „herhangi bir nesne“ gösterir, ama neden Ownership
Kurumsal uygulamalardan klasik bir durum: Bir import süreci her kayıt için yardımcı nesneler oluşturur (ör. StringLists, JSON-Parser, geçici listeler). İdeal akışta bunlar düzgün şekilde serbest bırakılır. Nadir durumlarda (doğrulama dolayısıyla atlama, Exception, erken çıkış) bir nesne asılı kalır. 10.000 kayıttan sonra bu görünür hale gelir.
FastMM FullDebugMode burada yardımcı olur, çünkü tahsis yerini gösterir. Ama “düzeltme” tahsisin yapıldığı yerde free çağırmak değildir. Düzeltme, sağlam bir Ownership-Pattern uygulamaktır:
- Bir nesneyi kim oluşturuyorsa otomatik olarak sahibi değildir.
- Ownership API sözleşmesinde açık olmalıdır (parametre/return, dökümantasyon, isimlendirme kuralları).
- Koleksiyonlar net ayrılmalıdır: owning vs. non-owning. Karışık kullanımlar intikamını alır.
- Exception yolları için erken try/finally blokları gereklidir.
Stacktrace’ten sadece “TStringList.Create” görüyorsan, bu bilgi değersiz değildir – ama sana sadece şunu söyler: burada bir şey oluşturuluyor. Asıl soru: nerede sonlanmalı? Ve burada hata ayıklayıcı akrobasiğinden ziyade mimari düşünce daha fazla yardımcı olur.
Stacktrace’leri doğru okumak: Bundan gerçekten ne çıkarabilirsin
FastMM’den gelen bir stacktrace genellikle, Debug sembolleriyle birlikte Units, prosedürler ve ideal olarak satır numaralarına eşlenen geri dönüş adresleri listesidir. Bunu okurken üç şey kritiktir:
- Yığının en üstü her zaman hata değildir: En üstteki frame’ler genellikle Memory-Manager/RTL olur. İlginç olan, kodunun başladığı yerdir.
- Tek satır yerine çağrı zinciri: Satır sadece bir noktadır. Zincir sana hangi yolun tahsise yol açtığını gösterir.
- Benzeyen birden çok blok: FastMM aynı boyutta birden çok leak bildiriyorsa, bu genellikle tekrarlayan bir yoldur. Bu iyidir: yeniden üretilebilirlik vardır.
Satır numaraları yoksa: MAP-Datei, Packages, Release-DCUs
Birçok ekip burada takılır: FullDebugMode aktif, leak raporu geliyor ama Unit/satır yerine sadece adresler veya kriptik semboller var. Tipik nedenler:
- MAP-Datei yok veya debug bilgileri üretilmemiş.
- Release-DCUs veya sembolsüz üçüncü taraf DLL’lerle çalışıyorsun.
- Uygulama Runtime Packages kullanıyor: o zaman kod parçaları BPL’lerde bulunur ve sembol çözümlemesi buna göre yapılmalıdır.
- Optimizasyon/inlining stacktrace’i daha okunmaz hale getirmiş olabilir.
Pratikte bunun anlamı: Leak avı için kasıtlı olarak “teşhis yapılabilir” bir build gerekir. Bu hedef „mümkün olduğunca hızlı“ olmaktan farklıdır. Teknik lead’ler bunu ayrı bir build-profil olarak ele almalı, böylece her ekip üyesi proje seçeneklerini rastgele değiştirmesin.
Frame’leri değerlendirmek: “İlginç” genellikle bir satır yukarıdadır
Gerçek hayattan bir örnek (belirli müşteri kodu olmadan): Stacktrace, kodunuzdaki ilk frame olarak bir rutin olan „LoadConfig“i gösterir. Orada bir nesne oluşturulduğunu görürsünüz. Bir Free eklersiniz, bellek sızıntısı ortadan kalkar – ve aniden başka bir yerde Double Free ile çakılır. Neden? Çünkü „LoadConfig“ nesneyi bir Cache’e koyar ve başka bir kod yolu zaten nesnenin sahibi (Owner) olup daha sonra temizliyor.
Doğru okuma şu olurdu: Stacktrace size blokun nerede oluştuğunu gösterir. Düzeltme genellikle şu tanımda yatar: Return sonrası nesnenin sahibi kim? Bu soruyu net cevaplamazsanız, yalnızca hata görünümünü değiştirirsiniz (Leak → AV).
Heap-Korruption vs. Leak: Warum FullDebugMode oft den echten Übeltäter findet
Pek çok „Leak“ gerçekte bir ikincil problemin sonucudur: Bir Buffer-Overrun heap-metedatayı ezebilir, bellek yöneticisi daha sonra düzgün şekilde serbest bırakamaz ve sonuçta görünüşte rastgele sızıntılar veya Invalid Pointer Operations görürsünüz. FullDebugMode bu durumda etkilidir; çünkü doğrulama desenleri kullanır ve Free/Reuse sırasında ek validasyonlar yapar.
Önemli olan ayrım şudur:
- Leak: Blok tahsis edilmiş ve hiç serbest bırakılmamış. Zaman içinde kararlılık azalır; çökme zorunlu değildir.
- Use-after-free: Blok serbest bırakılmış ama daha sonra hâlâ kullanılıyor. Tekrarlanmayan AV’lere yol açar; bunlar zor yeniden üretilebilir.
- Double Free: Blok iki kez serbest bırakılır. Hemen çakabilir veya blok yeniden kullanıldığında daha sonra patlayabilir.
- Heap-Korruption: Birisi bir bloğun sınırlarını aşıp yazıyor. Belirtiler genellikle zaman gecikmelidir.
FullDebugMode, semptomları zaman gecikmeli gördüğünüz durumlarda özellikle değerlidir. Ek validasyon hataları daha erken görünür kılar – çoğunlukla yanlış erişimin olduğu noktada, dakikalar sonra rastgele bir Free sırasında değil.
Vorgehen in Projekten: Reproduzierbare Leak-Jagd statt „Debugging im Nebel“
Eğer bellek sızıntılarını izlemek istiyorsanız, tekrarlanabilir ve ekip içinde paylaşılabilir bir süreç gerekir. Ben sabit bir teşhis çerçevesiyle çalışmayı tercih ederim:
1) Reproduktion in einem deterministischen Szenario
Güvenilir şekilde sızıntıyı gösteren bir test dizisi belirleyin: „Servisi başlat, 500 mesaj işle, servisi durdur“ veya „Form X’i aç, işlem Y’yi 200 kez çalıştır“. Önemli olan diziyi parametreleriyle birlikte belgelemektir (veri kümesi, Mandant, Feature-Flags), böylece başkaları da aynı adımları tekrar edebilir.
2) Minimieren: Leak pro Schritt sichtbar machen
Eğer dizin 20 dakika sürüyorsa, onu parçalara ayırın. Amaç: mümkün olduğunca hızlı şekilde „önce“ ve „sonra“yı karşılaştırabilmektir. Büyük uygulamalarda genellikle asıl zaman tüketeni düzeltmek değil, bu karşılaştırmayı hazırlamaktır.
3) FullDebugMode einschalten und Report interpretieren
Şimdi FastMM FullDebugMode devreye girer. Raporları toplayın, blok boyutuna/callstack’e göre gruplayın ve tekrarları kontrol edin. Tek bir kalan blok meşru bir önbellek olabilir. 10.000 aynı blok neredeyse her zaman gerçek bir sızıntıdır.
4) Ownership’in netleştirilmesi ve uygun katmanda düzeltme
Sızıntıları Ownership’in tanımlandığı yerde düzeltin: Factory, API-Vertrag, Collection-Wrapper. „Schnell ein Free einbauen“ doğrudan Create’in yanına koymak genellikle yanlış yerdir, eğer nesne iletiliyorsa.
5) Regression: gleiche Sequenz, gleicher Build, gleicher Report
Düzeltme ancak sekans tekrar çalıştığında ve ne sızıntı ne de yeni bellek hatası ortaya çıkmadığında iyi sayılır. Özellikle Use-after-free durumlarında „Leak weg“ bir kanıt değil, sadece yeni bir belirtidir.
Typische Fallstricke in Delphi-Code, die FastMM sichtbar macht
Collections und Ownership (Listen, Dictionaries, Interfaces)
Birçok sızıntı karmaşık algoritmalardan değil, günlük veri yapılarından kaynaklanır. İki klasik hata örneği:
- Bir liste nesneler içerir, ama kim bunları serbest bırakacak bilinmiyor. Çözüm: owning Liste kullanmak veya finally içinde tutarlı şekilde boşaltmak.
- Bir Dictionary nesneleri Value olarak tutar; Remove sırasında Value serbest bırakılmaz veya Clear sırasında unutulur.
Ek olarak Interfaces sorun çıkarabilir: Referans sayımı (ARC-ähnlich) konforludur, ancak nesne-Ownership ile karışık kullanım döngüsel referanslar veya event’ler nedeniyle sızıntılar oluşturabilir. FullDebugMode genellikle tahsis yolunu gösterir, ama neden bir referans döngüsüdür (A, B’yi Interface üzerinden tutar; B ise A’yı callback üzerinden tutar).
Exceptions und frühe Exits
Büyümüş kurumsal yazılım sistemlerinde istisnalar genellikle normal akışın bir parçasıdır (örn. doğrulama, iptal, yeniden deneme). Sorun nadiren istisnanın kendisidir, asıl sorun çevresindeki yoldur: Bir nesne try/finally’den önce oluşturulur, sonra bir istisna atılır ve cleanup atlanır. FullDebugMode size allokasyonun stacktrace’ini verir — ve garantili olarak yürütülen bir serbest bırakma yolu olup olmadığını kontrol etmelisiniz.
Threads und Lebenszeit: „Freigeben im falschen Thread“
VCL/FMX ve worker thread’li servislerde başka bir uç durum oluşur: Bir nesne bir thread’te oluşturulur ama UI-Thread’te serbest bırakılır (veya tersi), çünkü Queue/Synchronize ile „nur schnell“ bir şey aktarılır. Bu çalışabilir, ancak Producer çalışmaya devam ederken Consumer zaten serbest bırakıyorsa Use-after-free’a yol açabilir.
FastMM FullDebugMode burada yardımcı olabilir, çünkü zaman gecikmeli hataları daha erken tespit eder. Ancak asıl düzeltme temiz bir yaşam süresi modelidir: net sahiplik ilişkileri, yalnızca değiştirilemez verilerle aktarım veya belirgin Ownership transfer noktaları.
Wie du Reports nutzbar machst: Filtern, vergleichen, dokumentieren
Ekiplerde Leak-Reports’a sadece „bakmak“ yerine bir artefakt gibi davranmak faydalıdır. İşe yaradığı kanıtlanmış üç pragmatik önlem:
- Baseline-Report: Bir „bilinen durum“ (örn. mevcut ürün sürümü) bir kez FullDebugMode ile çalıştırılır ve referans olarak saklanır. Böylece yeni sızıntıları hemen fark edersiniz.
- Use-Case’e göre karşılaştırma: Kritik iş akışları (Import, Export, API-Request, UI toplu işlem) için her biri düzenli olarak tekrarlanabilir kısa bir sekans belirleyin.
- Belgelendirilmiş „legitime Leaks“: Eğer bir cache bilinçli olarak finalize edilmiyorsa, bunu belgeleyin. Aksi halde altı ay sonra biri yine aynı girdilerin peşine düşer.
Bu bürokrasi değil, zaman tasarrufudur: aksi takdirde bellek sızıntısı araması hızla sonsuz döngüye dönüşür, çünkü aynı desenler her sprintte yeniden ortaya çıkar.
Ne zaman uğraşmaya değer – ve ne zaman farklı yol izlemelisin
FastMM FullDebugMode, maliyeti olan bir tanı aracıdır. Çaba özellikle şu durumlarda karşılığını verir:
- Uygulama uzun süre çalışıyorsa (servis, Terminalserver-Client, vardiya sistemi, 7/24 süreçler).
- Gerçek müşteri veri akışları işliyorsan ve testte tüm yolları kapsamıyorsan.
- Kısa vadeli özellik hızından ziyade kararlılık daha önemliyse (süreç odaklı yazılım çözümlerinde tipik).
Öte yandan sadece 30 saniye sonra sonlanan küçük bir masaüstü yardımcı programın varsa, bellek sızıntısı araması genellikle ikincildir. Aynı şekilde: tek seferlik bir bellek zirvesi sorunun varsa (z. B. büyük bir dışa aktarma), çoğunlukla sızıntı değil, akış (streaming) stratejisi ve heap’teki zirve yük meselesidir.
Uygulama sonucu: FullDebugMode bir anahtar değil, bir süreçtir
FastMM FullDebugMode bellek hata araştırmasına yapı getirir: Allokasyonları görünür kılar, heap bozulmalarını daha erken tespit eder ve semptom yerine nedeni düzeltebilmen için stacktrace’ler sağlar. Ancak belirleyici kaldıraç araç değil, süreçtir: yeniden üretilebilir senaryolar, tanılamaya uygun build’ler, temiz sahiplik sözleşmeleri ve bir baseline’a karşı regresyon.
Eğer inatçı bir sızıntıda veya aralıklı heap hatasında takılı kalmışsan ve konuyu daha büyük bir Delphi-sistemde kalıcı olarak stabilize etmek istiyorsan, açık bir sıra ve değerlendirilebilir raporlarla kısa, temiz bir tanılama kurulumuna yatırım yapmak mantıklıdır. Analiz, build profilleri veya mimari refaktoring konusunda destek ihtiyacın varsa: Net-Base Software GmbH ile iletişime geç.
Bu konuda Delphi bellek sızıntısı bulma ve Fastmm Leak Report okuma da önemlidir. Makale bu hususları anlaşılır şekilde sınıflandırır ve günlük kullanımda nelere dikkat edilmesi gerektiğini gösterir.
Sonraki adım
Konu gerçek bir projeye dönüştüğünde, mimari, mevcut sistemler ve işletme erken dönemde birlikte değerlendirilmelidir.
Bireysel sorularda destek vermekle kalmıyoruz; kaynak kodu parçacıklarından, legacy konularından veya portal fikirlerinden sağlam bir kurumsal projeye dönüşene kadar da destek veriyoruz.
- Mevcut durum, hedef durum ve teknik riskler birlikte değerlendirilir.
- REST, veri erişimi, portallar ve Rollout daha sonra ortaya çıkan sonuçlar olarak ertelenmez.
- Hangi yolun ekonomik ve işletme açısından sürdürülebilir olduğunu erken görürsünüz.