Uygulamamızın kullandığı hafıza miktarını nasıl düşürebiliriz ?

// 16 Mayıs 2009 // Delphi, İşletim Sistemi, Programlama, Win32

Hepimizin bildiği gibi yazdığımız uygulamalar işletim sisteminde Process olarak adlandırılmaktadır. Her bir process en az bir main thread’e sahiptir. Bu iş parçacıkları, derlenmiş ikilik formattaki ham bilginin işletim sisteminin bellek yöneticisi vasıtası ile hafızaya yüklenerek işletilmesi ile hayat bulur. İşletim sistemlerinin bellek yönetimini nasıl yaptıkları son derece karmaşık bir konudur. Bir işletim sisteminde aynı anda birden fazla iş parçacığının çalışabilme ihtimali olduğu için bu işlemlerin bellekte kaplayacakları alanların ve bu bellek bölgeleri arasındaki etkileşimlerin son derece dikkatle hesaplanması gerekmektedir. İşletim sistemlerinde bir process fiziksel bellek ile iletişim halinde değildir. Bunun yerine her bir iş parçacığı için ayrılmış olan hafıza bloklarında her bir işlem kendisini fiziksel belleğin hakimi sanmaktadır. Bu tasarım, her bir işlem parçacığının teoride fiziksel belleğin kendisinden de daha fazla hafıza bloğuna sahip olabilmesini sağlamaktadır. Tüm bunları detaylıca anlatacak ne yerim ne de yeterince malümatım var ancak, bildiklerim ışığında izah etmeye devam edeyim. Process’lerin yani iş parçacıklarının fiziksel bellekten daha fazla alan kullanabilmesine imkan tanıyan teorik yaklaşıma Virtual Memory adı verilir. Virtual Memory modern işletim sistemlerinde disk vb. depolama cihazlarının kapasitelerinin bir kısmının fiziksel hafıza alanı gibi kullanması anlamına gelir. Örneğin Windows işletim sistemlerinde bu pagefile.sys dosyasıdır. İşletim sistemi bir iş parçacığının çalışması için gerekecek hafıza bloğunun karşılanamaması durumunda disk üzerindeki bahsi geçen dosyayı bir hafıza alanı gibi kullanarak fiziksel hafıza alanını rahatlatmaya çalışmaktadır. Ancak RAM’de tutulamayan verilerin sürekli disk ve RAM arasında yazılıp okunması elbette iş parçacığının çalışma performansını olumsuz yönde etkiler.

İşletim sistemleri, RAM ile sanal bellek arasındaki adreslemeleri fiziksel bellek haritasını kullanarak gerçekleştirirler. Her bir işlem için ayrılmış izole hafıza bloğundaki $101112 gibi bir adres fiziksel adresteki aynı adres demek değildir. Bu adresin fiziksel adresteki karşılığı başka bir tabloda tutulur ve fiziksel hafızaya erişim için bu tablodan istifade edilir. Modern işletim sistemleri, iş parçacıkları için ayırdıkları hafıza bloklarının tamamen izole olmasından sorumludurlar. Bir iş parçacığının diğer birisinin kullandığı hafıza bloğuna erişimi, o blokta yapabileceği değişiklikler tehlikeli durumlara sebebiyet verecektir. İşte bu sebeple arada sırada da olsa Protection Fault gibi hatalar alırız. Aynı zamanda işletim sistemleri, mevcut fiziksel hafızayı iş parçacıkları üzerinde adil dağıtma gibi bir misyonu da bünyelerinde barındırmak durumundadırlar.

Tüm bu karmaşık hususların gerçekleştirilebilmesi adına işletim sistemleri iş parçacıkları için onların kullanacakları reel hafızadan daha fazlasını ayırmak durumundadırlar. İşletim sistemleri paging adı verilen bu hafıza tahsisinde belli bloklarla hafıza rezerve edeceklerdir. Ayrılan hafıza bloklarınında ardışıl olmaması gibi sorunları da kendi içinde çözmesi gereken işletim sisteminin işinin ne kadar karmaşık ve kompleks olduğunu anladık sanırım.

Tüm bu genel kültürel bilgilerin ışığında, hepimizin farkettiği gibi yazmış olduğumuz uygulamalar işletim sisteminin bellek yöneticisi vasıtası ile hafızaya alındığında belli bir çalışma alanına sahip olur(Working Size). Bu çalışma alanı, uygulamamız içinde kullandığımız nesneler, değişkenler, threadler ile giderek daha da artabilir. Hatırlarsanız işletim sisteminin hafıza ayırma işlemini paging adı verilen karmaşık bir mekanizma ile yaptığını anlatmıştım. Bu mekanizma da işletim sistemi sizin gerçekte ayırmak istediğiniz hafıza bloğu 50k olsa bile her bir page size ne kadar ise o kadar ayıracaktır. Diyelim ki sizin ayırmak istediğiniz hafıza bloğu 40 kb ve page size’ında 100 kb olması durumunda, sizin bu işleminiz karşısında işletim sistemi 40 kb değil 100 kb yer ayıracaktır. Bu durumda sizin istemediğiniz 60 kb lık bir açık oluşacaktır!

Programlarınızın uzun süreçlerde hiç kapanmayacağı server uygulamalar yazıyorsanız eminim ki hafıza ile ilgili sorunlarla elbet karşılaşacaksınız. Peki bunun bir çaresi yok mu ? Elbette var.. Bunca sıkıcı bilgiden sonra sizi yazının akışına kaptıracak daha somut hususlara değinme zamanı geldi sanırım. Bildiğiniz gibi çalışan bir Windows uygulamasının ekranda görünür halden simge durumuna küçültülmüş(minimize) duruma geçmesi sırasında programın kullandığı hafıza bloğu son derece azalıyor. Peki bu nasıl oluyor da oluyor ?

İşletim sistemi tam bu noktada, programınızın Idle konuma alındığını düşünerek onun için hafızada ayırdığı alan üzerinde optimizasyon yapma cihetine gidiyor. Peki ne yapıyor derseniz, hafızada birbirinden kopuk şekilde ayrılmış memory segmentlerini birbirine daha yakın hale getirmeye çalışmak ve page size’dan ötürü fazladan ayrılan ve kullanılmayan hafızayı boşaltmak gibi işlemleri uyguluyor.

Peki biz bu işi nasıl yapacağız ;) Uygulamamızı kullanılmadığı zamanlarda minimize mi edeceğiz, kullanılmadığı zamanı nereden bileceğiz, başka yöntemi yokmu ?

Uygulamanızı idle olduğu zaman dilimlerinde park etmek yani minimize etmek bir çözüm evet. Ancak bunca karalama yapmamın nedeni elbette bu yöntemi tavsiye etmek değil. Daha şık bir yolu var. Ancak her şık yolda olduğu gibi bu yönteminde kendine has riskli senaryoları var.

Windows API Help’te SetProcessWorkingSetSize isimli API tam da bizim istediğimiz işi yapıyor. Bu API kendisine geçilen process için minimum ve maksimum bellek rezarvasyon miktarlarına göre uygulamanın kullandığı hafıza bloğunda genişletme yada daraltmaya gidiyor. SetProcessWorkingSetSize isimli API kernel32.dll içinde bulunmaktadır ve Delphi’de tanımı aşağıdaki şekildedir:

function SetProcessWorkingSetSize(hProcess: THandle;
  dwMinimumWorkingSetSize, dwMaximumWorkingSetSize: DWORD): BOOL; stdcall;

Minumum ve maximum parametrelerinin DWord olduklarını ve DWord ‘ün işaretsiz 32 bit bir veri tip olduğunu elbette biliyoruz. Bu parametrelere DWord’ün alabileceği maksimum değerler verilerek SetProcessWorkingSetSize API’sinin çağırılması uygulamamızın kullandığı hafıza bloğunun küçültülmesi anlamına gelecektir.

Artık yavaş yavaş kod bloklarını vermeye başlayabiliriz.

 
  procedure ShrinkMemory;
  var
    myProcess : THandle;
    dwMin,
    dwMax : DWord;
  begin
    dwMin := High(DWord); // Yada $FFFFFFFF
    dwMax:= High(DWord); // Yada $FFFFFFFF
    myProcess := GetCurrentProcess;
    SetProcessWorkingSetSize(myProcess, dwMin, dwMax);
  end;

Yukarıda yazdığımız ShrinkMemory prosedürümüz gerçekten de uygulamamızın kullandığı hafıza bloğunu küçültmektedir. Ancak bu prosedürü kullanırken son derece dikkatli hareket etmemiz gerekir. Çünkü işletim sistemi gerçekte uygulamanın kullandığı hafıza bölgesini sıfırlayıp küçültülmüş hali ile çok kısa bir zaman diliminde yeniden yüklemektedir. İşletim sisteminin bu işlemi yaptığı zaman diliminde sizin uygulamanızın başka herhangi bir işlem yapmıyor olmasının hayati derecede önemi vardır. Yoksa veri kayıplarına ve çeşitli ciddi hatalara açık duruma gelirsiniz. O halde yapmamız gereken tek şey, ilgili ShrinkMemory metodunu uygulamamızın Idle yani kullanılmadığı bir zaman diliminde çağırmak olacaktır.

Peki uygulamamızın belli bir müddet kullanılmadığını nereden bilebiliriz ? Bunun bir kaç yolu var. Ben öncelikle size kullanmamanız gereken yöntemi göstereceğim ondan sonra nihai yöntemi aktaracağım. (Sanırım bende her programcı gibi biraz mazoşistim :) )

function SecondsIdle: DWord;
var
   liInfo: TLastInputInfo;
begin
   liInfo.cbSize := SizeOf(TLastInputInfo) ;
   GetLastInputInfo(liInfo) ;
   Result := (GetTickCount - liInfo.dwTime) DIV 1000;
end;

Bu fonksiyon adından da anlaşılacağı üzere uygulamanın kaç saniye boyunca idle olduğunu anlamanıza yarayacak bir metoddur. Ancak bunu kullanmayacağız. Neden mi ? Çünkü bu fonksiyon işletim sistemindeki giriş aygıtlarının yani klavye mouse gibi ne kadar zamandır kullanılmadığını gösteren işletim sisteminin genelininin bilgisini döndüren bir metoddur. Kısacası sizin uygulamanız kullanılmaz durumda olduğu halde(mesela park halinde) başka uygulamalar üzerinde mouse yada klavye ile birşeyler yapıyorsanız uygulamanızı hâla kullanıyormuşçasına değer döndürecektir bu fonksiyon. Denemesini yapabilirsiniz. O halde biz nihai yöntemimizi sizlerin beğenisine sunalım;

var
  LastTick : DWord = 0;
  ..
  ..
  ..
  ..

//Timer1 : TTimer türünde Interval 1000
//ApplicationEvents1 TApplicationEvents türünde ve OnMessage olay yöneticisinde aşağıdaki kod var.

procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
begin
   case Msg.message of
    WM_LBUTTONDOWN	,
    WM_LBUTTONDBLCLK,
    WM_RBUTTONDOWN	,
    WM_RBUTTONDBLCLK,
    WM_MOUSEMOVE	,
    WM_KEYDOWN		: LastTick := GetTickCount;
  end;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  Timer1.Enabled := true;
  LastTick := GetTickCount;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  Diff : Extended;
begin
  Diff := (GetTickCount - LastTick) / 1000;

  if Diff >= 60 then // 60 sn boyunca uygulamam kullanılmaz ise...
  begin
    ShrinkMemory;
    LastTick := GetTickCount;
  end;
end;

Yukarıda yazdığımız kod örneği beklendiği şekilde çalışacaktır ancak hâla risklere açık bir hali vardır. Uygulamanız içinde 60 saniyeden daha uzun sürebilecek bir işlem olduğunda(mesela rapor almak amacı ile uzun süren bir sorgunun çalıştırılması vb.) uygulamanıza yönlendirilecek windows mesajları(klavye/mouse vb) uygulamanızın işletim kuyruğuna alınacaklarından henüz işletilmeyecekler ve hafıza küçültme kodumuz devreye girebilecektir. Bu da istenmeyen ciddi sorunlara neden olabilir. Bu durumlar hasıl olacaksa siz siz olun ilgili Timer nesnesini uzun sürecek işlemin başlangıcında durdurun, bitişinde yeniden başlatın. Umarım faydalı olabilmişimdir.

Sevgiler, saygılar..

“Uygulamamızın kullandığı hafıza miktarını nasıl düşürebiliriz ?” için 8 Yorum

  1. Veli BOZATLI diyor ki:

    Yine yararlı, yine bol emek verilmiş, yine değerli bilgiler. Tabiki yine Allah razı olsun :)
    Programımızın kullandığı hafıza miktarında sürekli artış varsa kesinlikle bir problem olduğu mânâsına mı gelir?
    Büyük çaplı bir projede böyle birşey farkettim ve FastShareMem + MemCheck ile kontrol ettiğimde onlarca Memory Leak tespit etti. Bunların tek tek düzeltilmesi gerek-şart mıdır?

  2. Tuğrul HELVACI diyor ki:

    Allah sizden de razı olsun. Evet memory leak’lere son derece dikkat etmek gerekir. Bir uygulamanın sürekli surette kullandığı hafıza miktarının artması program içinde memory leak olduğunun göstergesidir.

    Create ettiğimiz nesnelerin uygun zaman ve yerde Free edilmeleri çok önemlidir. .Net vb platform dillerinde GC adı verilen Garbage Collector ara ara devreye girerek(ne zaman devreye gireceği hakkında net bir malümat yok) kullanılmayan nesneleri temizleme cihetine gider.

    Lâkin bizim Delphi’de böyle prematüre mekanizmalara ihtiyacımız yok. En güzel mekanizmalardan bir tanesi olan Owner mekanizması Delphi’mizde zaten mevcut. Bir nesne Create edilirken içine aldığı parametre genelde anlam yönünden bilinmez. Ama o Owner’dır. Ve owner kendisi yok olmadan evvel sahip olduğu nesneleri kontrol ederek onlara “yok olmaları” gerektiğini söyler.

    Yada bir diğer mekanizma tasarlayacağınız sınıfların belirli bir interface’i implemente etmeleri olabilir. Bu sınıfın instance’larını interface’lere atarsanız kapsam alanından(scope) çıktıklarında otomatikman temizleneceklerdir.

    Dikkatli programcı, oluşturduğu nesneyi free edendir ;)

  3. celalettin diyor ki:

    merhaba, bahsettiğiniz konuyut test ettim, programı min. ettiğimde bellek kullanımı azalııyor ancak sanal bellek aynen kalıyor, sanal bellek nasıl azaltılabilir? kullandığım programdan rapor alındığında bellek kullanımı ve sanal bellek büyüyor, raporu kapatınca bellek azalmıyor, bu durumun sebebi ne olabilir.

    saygılar

  4. Tuğrul HELVACI diyor ki:

    Merhaba Celalettin bey,

    Bildiğiniz gibi işletim sistemi sanal belleği sadece fiziksel belleğin yetersiz kaldığı durumlarda kullanır. Ama genellikle bu sanal bellek dosyası zaman zaman büyür. Sanal bellek dosyasının diskte var olması işletim sisteminin o anda sanal bellek dosyasına müracaat ettiği manasını taşımayabilir. Fiziksel hafızanın kapasitesi, o anda çalışan tüm iş parçacıklarının kapladıkları hafıza miktarını karşılamaya yetiyorsa sanal bellek ile fiziksel bellek arasında veri takası yapılmasına ihtiyaç duyulmaz.

    Bahsettiğiniz senaryodan anladığım kadarı ile makinanız üzerinde fiziksel hafızanın kapasitesini aşan bir hafıza kullanımı var gibi görünüyor. Bu gibi bir durumda sanal belleğin belirli periotlarla büyüyüp-küçülüyor olması beklenen bir durumdur.

    Ancak; şunu test etmeniz faydanıza olacaktır. Uygulamanız içinde ShrinkMemory(SetProcessWorkingSetSize) metodu çalıştırılmadığı halde, rapor aldıktan sonra uygulamanızın hafızası eski konumuna gelmiyor ise; o zaman muhtemel bir Memory Leak’ten söz edilebilir.

    Eğer Delphi 2010 ile yazılım geliştiriyorsanız; projenizin kaynak kodunda ReportMemoryLeaksOnShutDown := true; satırını aktif hâle getirin; Delphi 2010 kullanmıyorsanız EurekaLog gibi bir yazılımın demo sürümünü indirip Delphi’ye kurun ve muhtemel Memory Leak’leri tespit etmeye çalışın.

    Tüm incelemelerinize rağmen; memory leak’in sizin kodlarınızda olmadığına kanaat getirirseniz; kullandığınız componentlerin bu soruna neden olduğu kanaatini edinebilirsiniz.

  5. Mustafa Halil diyor ki:

    Merhaba,
    Tuğrul Bey, öncelikle teşekkürlerimi sunuyorum.

    Uzun zamandır kafama takılan bir konuydu,aslında uygulama içerisinden müdahele yerine ayrı bir uygulama olarak yazıp istediğim programa dahil etmeyi düşünüyorum.Bir bileşen olarak tasarlamak için başlangıç yapmaya az kaldı.
    Projemde hafıza sızması yok,ancak grafik ağırlıklı prejelerimden tecrübe edindiğim üzere bellek kullanımı oldukça artmakta.
    Teoride oluşturduğumuz nesneyi; işi bitince silmek, lazım olduğunda tekrar oluşturmak çok çok mantıklı bir iş iken pratikte bu o kadar da göründüğünü veren bir yapı olmuyor(hafıza yüzünden).

    Deneme amaçlı çalışma zamanında yüzlerce kontrol oluşturuluyor diyelim.Bunlar silindiğinde kullandığı bellek alanını kendisi sisteme geri veren bir mekanizmanın olması artık şart.
    Ancak hali hazırda bunu yapan araçlar ” Bellek İyileştirme, Bellek Optimizasyon …vs programları ” bir çok insan tarafından kullanılmakta.Kullanıldığına göre ihtiyaç var demektir.
    Makalede yer alan bellek iyileştirmesi aslında çokta tehlike yaratmıyor.Ben uzun süredir benzer bir yapıda kurguladığım bellek iyileştirmeyi kullanmaktaydım ancak hatalı bir yerler vardı ve artık değişim zamanı geldi.

    Bellek iyileştirmeyi pat diye yapmaya kalkınca hatalarla karşılaşmak muhtemel.
    Ancak makaleyi uyguladığımda kafama takılan bir şeyler oldu.Bunları paylaşmak istiyorum.

    ApplicationEvent Nesnesi tıklama,vs… durumlarda son tiki değiştirmekte.
    Timer ShrinkMemory yi çalıştırmakta,
    Formun onShow olayında ise Timer nesnemizi çalıştırıyoruz.Ve timer nesnesi biz durdurana kadar çalışmaya devam ediyor.Kontrolü 60 saniye yerine 10 saniye ye çektim ve Timer nesnesinin onTimer olayında bulunan kodları try-finally bloğuna aldım.
    Finally kısmında da Timer1.Enabled := False; ile timer ı devre dışı bıraktım.

    Demek istediğim timer sadece belleği düzenleyecek ve kapanacak bir yapıda olsaydı diye düşündüm.Bu seferde Formun onShow olayında açtığımız timer nesnemizi dışarıdan açmamız gerekliydi.Ama hangi tetikleyiciyle?
    Çünkü kaç saniye işlem yapılmadığını tespit eden nesne timer nesnesi ama kapalı durumda.Yani nasıl bulacağız kaç saniye kullanmadığını?

    İşte burada timer yerine sabit bir prosedür yazma gereği duydum.Yani timer nesnesinin onTimer kodlarını bir prosedür içine aldım.Ancak bir kez çalışıyor ve bir daha program kapatılana dek çalışmıyor.

    Yani aslolan bize kullanılmayan zamanı otomatik olarak verecek bir yapı.
    Bu yapıdan sonra herşey tamam olur kanaatindeyim.

  6. Mustafa Halil diyor ki:

    Merhaba,
    Tuğrul Bey, öncelikle teşekkürlerimi sunuyorum.

    Uzun zamandır kafama takılan bir konuydu,aslında uygulama içerisinden müdahele yerine ayrı bir uygulama olarak yazıp istediğim programa dahil etmeyi düşünüyorum.Bir bileşen olarak tasarlamak için başlangıç yapmaya az kaldı.
    Projemde hafıza sızması yok,ancak grafik ağırlıklı prejelerimden tecrübe edindiğim üzere bellek kullanımı oldukça artmakta.
    Teoride oluşturduğumuz nesneyi; işi bitince silmek, lazım olduğunda tekrar oluşturmak çok çok mantıklı bir iş iken pratikte bu o kadar da göründüğünü veren bir yapı olmuyor(hafıza yüzünden).

    Deneme amaçlı çalışma zamanında yüzlerce kontrol oluşturuluyor diyelim.Bunlar silindiğinde kullandığı bellek alanını kendisi sisteme geri veren bir mekanizmanın olması artık şart.
    Ancak hali hazırda bunu yapan araçlar ” Bellek İyileştirme, Bellek Optimizasyon …vs programları ” bir çok insan tarafından kullanılmakta.Kullanıldığına göre ihtiyaç var demektir.
    Makalede yer alan bellek iyileştirmesi aslında çokta tehlike yaratmıyor.Ben uzun süredir benzer bir yapıda kurguladığım bellek iyileştirmeyi kullanmaktaydım ancak hatalı bir yerler vardı ve artık değişim zamanı geldi.

    Bellek iyileştirmeyi pat diye yapmaya kalkınca hatalarla karşılaşmak muhtemel.
    Ancak makaleyi uyguladığımda kafama takılan bir şeyler oldu.Bunları paylaşmak istiyorum.

    ApplicationEvent Nesnesi tıklama,vs… durumlarda son tiki değiştirmekte.
    Timer ShrinkMemory yi çalıştırmakta,
    Formun onShow olayında ise Timer nesnemizi çalıştırıyoruz.Ve timer nesnesi biz durdurana kadar çalışmaya devam ediyor.Kontrolü 60 saniye yerine 10 saniye ye çektim ve Timer nesnesinin onTimer olayında bulunan kodları try-finally bloğuna aldım.
    Finally kısmında da Timer1.Enabled := False; ile timer ı devre dışı bıraktım.

    Demek istediğim timer sadece belleği düzenleyecek ve kapanacak bir yapıda olsaydı diye düşündüm.Bu seferde Formun onShow olayında açtığımız timer nesnemizi dışarıdan açmamız gerekliydi.Ama hangi tetikleyiciyle?
    Çünkü kaç saniye işlem yapılmadığını tespit eden nesne timer nesnesi ama kapalı durumda.Yani nasıl bulacağız kaç saniye kullanmadığını?

    İşte burada timer yerine sabit bir prosedür yazma gereği duydum.Yani timer nesnesinin onTimer kodlarını bir prosedür içine aldım.Ancak bir kez çalışıyor ve bir daha program kapatılana dek çalışmıyor.

    Yani aslolan bize kullanılmayan zamanı otomatik olarak verecek bir yapı.Ve bunu gönderebileceği parametreli bir fonksiyon,bu fonksiyonda bellek iyileştirmeye yapacak.
    Bu yapıdan sonra herşey tamam olur kanaatindeyim.
    Her ne kadar tam olarak bellek yönetimi yapamasakda.
    Yapılamazmı? -> Hayır, yapılabilir ancak her oluşturduğumuz kontrol için bellek ayırmak,bitince ayırdığımız yeri sisteme vermek şartıyla.Ancak yinede tam anlamıyla olmamakta.Tamamını kontrole alabilmek için başlı başına bellek yönetim sistemi yazılması gerektiğinin farkına varacaksınız.Kod yazma sürenizde bellek yönetimi için ayırdığınız kodların sadece 5 te 1 ine sahip olacak ama sonunda istikrarlı bir yapınız olacak.

    Makale için Allah Razı olsun,
    Saygılarımla…

  7. Tuğrul HELVACI diyor ki:

    Yorumunuza teşekkür ederim Mustafa bey. Allah cümlemizden razı olsun.

    Kafanıza takıldığını söylediğiniz hususu okudum, ancak neden timer’ı devre dışı bıraktığınızı anlamadım. Memory’nin bir kereye mahsus shrink edilmesi uygulamanın çalışmasının uzun sürmesi durumunda bir işe yaramayacaktır. Belirli periotlarda memory’de iyileştirme yapmaya ihtiyaç hasıl olduğu için ilgili ShrinkMemory kodu bir timer’ın içerisindedir.

    Gerçek bir bellek organizasyonu yapacak kadar derin işletim sistemi bilgisine ve yeteneğine sahip değilim, Microsoft beni işe almadığı sürece de bu kadarının fazla olacağı kanaatindeyim. Bizim programlarımızda dikkat etmemiz gereken yegane unsur Memory Leak’lerdir. Memory Leak’ler pek çoğumuzun bilerek yada bilmeyerek karşılaştığı çok önemli programlama hatalarındandır. Memory Leak’lerin yol açtığı hafıza taşmalarının bu makaleye konu olan yöntem ile aşılması kesinlikle önerilmez.

    Bir programcı; runtime sırasında oluşturduğu tüm nesneleri atadığı değişkenin aslında bir işaretçi(pointer) olduğunu bilirse ve bu pointer’ın gösterdiği adresi içeriğini boşaltmadan değiştirmiyor ise sorun yok demektir. Zaten Delphi mükemmel bir şekilde Observer Pattern benzeri bir mekanizmayı biz Delphi programcılarının kullanımına sunmuştur. Bu mekanizmanın adı Owner’dır. Owner mekanizması biz bazı nesneleri yok etmeyi unutsak dahi, memory leak’ler ile karşılaşmamızın önüne geçmeye yardımcı olur.

    Bizler ne kadar yetenekli programcılar olursak olalım, bazen hafıza taşmalarının önüne geçemeyebiliriz. Bunun sebebi elbette bizden kaynaklanmayan, işletim sistemi çalışma mekanizmasından kaynaklanan sorunlar olabilir. Bazen herşeyin normal gittiği durumlarda dahi olmadık hatalarla karşılaşabiliriz. Örneğin; Out of memory sık karşılaşılabilmesi olası bir hatadır.(Elbette günümüzde ram kapasitelerinin çokluğu bu hata ile karşılaşma ihtimalimizi epey azaltmıştır.)

    Uygulamamızda oluşturduğumuz tüm kontrollerimizin, yada işletim sistemi nesnelerinin sayısı yeterince çok olmadığı halde yada kullandığımız ve işimizin bittiği nesneleri uygun bir metod ile hafızadan temizledikten sonra dahi neden Out Of Memory gibi bir hata alıyor olabiliriz ?

    Aslında bu sorunun yanıtı da basittir. İşletim sistemi üzerindeki bazı nesnelerin belirli bir sayısı vardır ve işletim sistemi üzerinde çalışan tüm uygulamalar bu sayıyı ortak bir şekilde paylaşırlar. Örnek vermek gerekir ise, tüm işletim sistemi üzerinde oluşturulabilecek görsel GDI nesne sayısının bir sınırı vardır. İşletim sistemi üzerindeki herhangi bir uygulama mevcut GDI nesnelerinden fazlası ile oluşturuyor ama yok etmiyor ise, sizin masum uygulamanızın kodları bir grafiksel nesne oluşturup onun tutacağını(Handle) almayı bekler iken bir de bakmışsınız Out Of memory hatası almışsınız.!

    Görüldüğü üzere, uygulamamızın stabil çalışması sadece bizim dikkatimiz ve hünerimiz ile değil, bir bütünün uyumu ile gerçekleşebiliyor.

    Siz ne kadar iyi şoför olursanız olun, bir başka acemi gelip size çarpabilir.!

  8. Mustafa Halil diyor ki:

    Cevabınız için teşekkür ederim Tuğrul Bey,
    haklısınız bir süreden sonra Timer çalışır vaziyette de olsa, çalışmasa da ShrinkMemory bir işe yaramıyor.Anlatmak istediğimde buydu zaten bunun için kodu timer dışına taşıdım.Timer dışına taşıyınca kod saece bir kez çalıştırıyorsunuz ve birdaha çalıştırdığınızda bi bakmışsın bir değişiklik yok.
    Timer içerisinde ise ard arda 3-4 defa çalışıyor, ama sonra o da suçluluğunu anlayayıp durduruluyor sanki :)

    Pointer, Owner, Handle -> Bunlarla uğraşmak lazım evet.
    Ama sizde bizde biliyoruz ki işimiz artık o kadar da basit olmayacaktır.Yukarıda dediğim gibi bunlarla uğraşırsak program pointer,handle ile ilgili yazdığımız kodun sadece 5 te 1 ine sahip olur.Küçük bir projede önemli değil ama projenin büyüdüğünü düşünürseniz.Kullanılan bellek kısmını iade etmek için bile onlarca kontrol yapacaksınız o pointer ı kullanan başka bir yer varmı diye.
    Ve sonra iade ettiğiniz yerlerin anında geri alınmadığı hissine kapılacaksınız,zaten sizin kullanmayıp – iade verdiğiniz yer de bellek kullanımında görünmeyecek.
    - eee, sorun nerde? İşte ShrinkMemory i burada kullaıp moralinizi düzelteceksiniz :)
    Tabi herşey 4 x 4 ama bu seferde Out Of Memory denilmezse…

    (Bu arada ilk yorum 2 kez gönderilmiş, Herkesten özür dilerim, kusuruma bakmayın yanlışlıkla oldu galiba)

    Hepinize kolay gelsin.Allah Razı olsun Tekrar…
    Saygılarımla…

Yorum Yazın