Bellek yönetimi

Ana belleğin işlemler arasında paylaştırılmasına ana bellek yönetimi ya da bellek yönetimi (Memory Management) adı verilir. Bellek yönetimi dendiğinde genellikle belleğin yığın (heap) alanında tahsis edilmiş olan bellek alanlarının yönetimi kastedilir.

İşletim sisteminde yürütülen programlar ya da gömülü sistem yazılımları kendi içlerinde bellek yönetimi sergileyebilirler. IBM OS/360 ve ardıllarında bellek işletim sistemi tarafından yönetilirken, Unix ve benzeri işletim sistemlerinde bellek uygulama tarafından yönetilir.

Yazılım geliştirmede, bellek yönetimi ilk anlardan beri önemli bir konu olmuştur. Programlama dili teorisi alanında yaşanan gelişme ve iyileştirmeler, yazılım geliştiricilerin farklı yaklaşımları ve ihtiyaçları nedeniyle bellek yönetimi konusunda pek çok yöntem ortaya çıkmıştır. Bu yöntemler genellikle otomatik bellek yönetimi ve manuel bellek yönetimi olacak şekilde ikiye ayrılmaktadır.

Tahsis edilmiş bellekler serbest bırakılana kadar tekrar tahsis edilemezler. Bu nedenle artık kullanılmayan bir bellek tahsisinin serbest bırakılması önemlidir. Tahsis edilen bellekler artık ihtiyaç duyulmadığı takdirde serbest bırakılmazlarsa, bellek sızıntısına (memory leak) neden olurlar.

Amaçlar
Bir işletim sisteminin bellek yönetiminin sonuçları şunlardır:

Bellekteki herhangi bir işlemi başka bir yere aktarabilmelidir.
Birden fazla işlem veya kullanıcı olduğunda bir kullanıcını diğer kullanıcını alanlarına girmeleri önlenmelidir.
Kullanıcılar arası kaynak paylaşımını sağlamalıdır.
Belleğin mantıksal alanlara bölünmesini sağlayarak bilgiye erişimi kolaylaştırmalıdır.
Belleğin yetmediği durumlarda fiziksel başka bellek alanlarını yani hard diskleri kullanabilmelidir.

Manuel Bellek Yönetimi
Bir yazılım çalışma zamanında (runtime) bellek tahsisi (allocation) gerçekleştirmek istediğinde çekirdek (kernel) ile iletişim halindedir. Tahsis yöntemi farklılık göstermekle birlikte işletim sisteminin bellek yöneticisi tarafından ayrılan tahsisin konumu önceden bilinmediğinden bir referans (işaretçi / pointer) ile erişilir. Program tarafından tahsis edilen bu alanlar programın kendi içerisinde sergilediği bellek yönetimine kalmaktadır. Program tahsis ettiği alanları artık ihtiyacı kalmadığı durumlarda serbest bırakarak (deallocation) belleği geri kazandırır ve belleğin ilgili bölümleri artık tahsis edilebilir hale gelir. Bu tahsisler geliştirici tarafından manuel bir şekilde gerçekleştirilir ve serbest bırakılır.

Geliştirici ne zaman bellek tahsis etmesi ve serbest bırakması gerektiğine kendisi karar vermelidir. Geliştiricilerin bellek yönetimini verimli ve doğru bir şekilde gerçekleştirmemeleri çeşitli bellek sorunlarına yol açabilir. Örneğin C programlama dilinde artık kullanılmadığından serbest bırakılması gereken tahsisler serbest bırakılmadığında bellek sızıntısına (memory leak) neden olabilir. Bu nedenle geliştirici tahsisleri sürekli olarak gözetmeli ve doğru konumlarda artık serbest bırakmaya özen göstermelidir.
include
include

int main() {
int n = 20;

// n tane int tutabilecek bir bellek alanı tahsisi
int *list = (int*)(malloc(n*sizeof(int)));

/*
programın işevi
*/

// Tahsis edilen belleğin serbest bırakılması
free(list);
list = NULL;

return EXIT_SUCCESS;
}

Olumlu Yönleri
Manuel bellek yönetiminde programın bellek yönetimi üstünde yazılım geliştirici tam yetkilidir. Ne zaman tahsis edileceğine ve serbest bırakılacağına tamamen kendisi karar verebilir. Bu nedenle bellek tahsisleri konusunda istediği şekilde yönetim sergileyebilir.

Manuel bellek yönetiminde yazılım geliştirici tarafından tahsis yönetimi yapıldığından, genellikle bellek tahsis etmek, serbest bırakmak ve yeniden boyutlandırmak gibi temel işlevler kullanılır, bu işlevler çoğu uygulamada (implementation) ara algoritmalar yoktur ve yalnızca işlevler görevini yaparlar. Bu nedenle manuel bellek yönetimi performansı en düşük oranda etkileyen bellek yönetimi yöntemlerinden biridir.

Olumsuz Yönleri
Bellek tahsislerinin yazılım geliştiriciler tarafından yönetilmesi gerektiğinden çeşitli güvenlik sorunlarına yol açabilecek hatalar yapılabilir. Örneğin tahsis edilen belleğin gözden kaçarak serbest bırakılmaması sonucu bellek sızıntısı meydana gelebilir ya da tahsis edilen belleğin doğru kullanılmamasından kaynaklı olarak çeşitli bellek hataları yapılabilir.

Bu yöntem yazılım geliştirme sürecinde önemli bir efor gerektirebilir, geliştirme ve test sürecini uzatabilir.

Otomatik Bellek Yönetimi
Otomatik bellek yönetimi, bellek yönetimini otomatik hale getiren yöntemlere verilen genel isimdir. Pek çok modern programlama dilinde otomatik bellek yönetimi yaklaşımına rastlanmaktadır. Çalışma zamanında programın bellek yönetiminin kendi içerisinde otomatik bir şekilde gerçekleştirilmesine dayanır. Bu yaklaşımın yaygın örneklerinde geliştiricinin bellek yönetimini düşünmesi gerekmemektedir. Otomatik bellek yönetimi içerisindeki tekniklerin mantığına dayanan ancak birebir aynı olmayan uygulamaları (implementation) bulunmaktadır.

Olumlu Yönleri
Yazılım geliştiriciyi bellek yönetimini düşünmekten büyük oranda ya da tamamen kurtarır. Yazılım geliştirme sürecindeki belek yönetimi kaynaklı hataları azaltır, maliyeti ve geliştirme zamanını düşürür. Bellek yönetimi kolay hale geldiğinden ilgili programlama dilini kullanarak geliştirme yapmak daha kolay hale gelir, bu nedenle teorik olarak daha fazla geliştirici tarafından kullanılabilir.

Olumsuz Yönleri
Yazılım geliştiricinin yazılımın bellek yönetimi konusundaki yetki alanlarını sınırlandırır ve kontrolünü azaltır. Çoğu durumda, belleğin incelikle yazılım geliştirici tarafından yönetilmesi gereken senaryolarda iyi bir seçenek değildir.

Performans açısından kritik yazılımlar için olumsuz etki doğurabilir. Çöp toplama tekniğine göre programın çalışma zamanında (runtime) büyük oranda çöp toplama yükü bulunabilir, ayrıca yönteme göre daha fazla bellek kullanımı gibi sorunlara yol açabilir.

Çöp toplama genellikle otomatik olarak derleyici (compiler) tarafından uygulanan bir derleyici özelliği olduğundan, derleyici kaynaklı bir bellek yönetimi hatası meydana gelmesi durumunda yazılım geliştirici bu sorunun çözümü için daha eski derleyici sürümleri kullanmak zorunda ya da daha yeni sürümleri beklemek zorundadır.

Çöp Toplama (Garbage Collection)
Çöp toplama (Garbage Collection), program tarafından tahsis edilen ve artık kullanılamayan/erişilemeyen tahsislerin serbest bırakılmasına dayanan bir otomatik bellek yönetimi biçimidir, artık ihtiyaç duyulmayan bellek tahsislerine çöp (garbage) denir. Çöp toplama bir programın toplam çalışma süresinin önemli bir bölümünü oluşturabilir ve performansa olumsuz etki edebilir. Çöp toplamanın yürütülmesi öngörülemez olabilir, bu da yapılan performans testlerinde çöp toplama kaynaklı değişken sonuçlar elde edilmesine yol açabilir. Bununla birlikte programda dağılmış olarak önemli duraklamalar meydana gelebilir, bu duraklamalar çöp toplayıcı yürütüldüğü esnasında programın yürütülmesinin kilitlenmesinden kaynaklanmaktadır.

Çöp toplama kullanan dillere çöp toplanmış diller (Garbage Collected Languages) denir. Bir dilin ana tasarımı birden fazla bellek yönetim biçimini barındırabilmektedir. Örneğin bazı derleyiciler çöp toplama ve manuel bellek yönetimi arasında geliştiriciye bir seçim yapma imkânı sunmaktadır. Bir dilin çöp toplama sergiliyor olması için çöp toplamanın dilin ana tasarımına dahil olması ya da bir derleyici özelliği olması gerekmemektedir. Örneğin manuel bellek yönetimi sunan C ve C++ programlama dilleri için çeşitli çöp toplama uygulamaları (implementation) (Bkz. Boehm Garbage Collector) geliştirilmiştir.

Çöp toplama için referans sayımı ya da sahiplik gibi farklı yöntemler geliştirilmiştir.

İzleme (Tracing Garbage Collection)
Çöp toplama tekniği yaygın olarak çöp toplama takibi (Tracing Garbage Collection) ile gerçekleştirilir. Bu o kadar yaygın bir uygulamadır ki genellikle çöp toplama dendiğinde çöp toplama takibi kastedilmektedir. Çöp toplama izleme program içerisinde gerçekleştirilen tahsislerin kullanımlarını analiz eden ve kullanımda olmayan tahsisleri, bu tahsislere çöp (garbage) denir, tespit edip serbest bırakan bir çöp toplama tekniğidir.

Çöp izlemenin gerçekleştirilmesi için bazı izleme verilerinin çalışma zamanında depolanması gerekmektedir, bu da bir ek yük olarak yansımakta ve yazılımın bellek ayak izini artırmaktadır. Bu izlemenin gerçekleştirilmesi için kullanılan pek çok algoritma bulunmaktadır. Çöp toplama takibi bir derleyici özelliği olabilir ve derleyici tarafından çalışma zamanının bir parçası olarak sunulabilir. Örneğin C# ve Java programlama dillerinde çöp toplama takibi kullanılmaktadır ve bu yaklaşım çalışma zamanında tamamen otomatik bir şekilde yürütülür. Oluşturulan ilgili nesneler otomatik olarak çöp toplama takibine dahil edilir. Bir diğer yöntemde ise çöp toplama takibine dahil edilecek tahsislerin geliştirici tarafından bildirilmesi gerekir.

Nesne sonuçlandırma (object finalization) zamanlamasında çöp toplama takibi deterministik değildir. Çöp toplamaya tabii olabilecek duruma gelen her nesne eninde sonunda serbest bırakılacaktır lakin bunun gerçekleşme durumu öngörülemez. Çöp toplamanın ne zaman gerçekleşeceğine ya da gerçekleşmeyeceğine dair hiçbir garanti yoktur. Çöp toplama işlemi programın asıl algoritmasından kaynaklanmayan gecikmelere ve duraklamalara neden olup yürütme zamanının oldukça değişken bir hale gelmesine yol açabilir. Deterministik olmayan yapısı nedeniyle yeni bir nesne tahsis etmek bazen yalnızca nesnenin tahsis edilmesi şeklinde gerçekleşirken bazen uzun bir çöp toplama döngüsünün yürütülmesini tetikleyebilir.
package main

import "fmt"

type Car struct {
Brand string
Model string
}

func main() {
// ptr heap tahsisli bir Car barındırmaktadır
// tahsisin izlenmesi ve serbest bırakılmasını çöp toplayıcı üstlenmektedir
ptr := &Car{"Ferrari", "330 P3"}

fmt.Println(ptr.Brand, "-", ptr.Model)
}

Referans Sayımı (Reference Counting)
Referans sayma ilgili tahsise yapılan her bir referansın sayılması ve referansların hepsinin sıfıra ulaşması durumunda tahsisin serbest bırakılmasına dayanır. Bu teknikte her referans kendisine referans eden bir sayma verisi bulundurur. Bu sayma verisi referans her kopyalandığında artırılır ve kopya her yok edildiğinde azaltılır. Referans sayma verisi sıfıra ulaştığında tahsisi kullanan bir program noktası olmadığından serbest bırakılır.

Referans sayma çöp toplama takibinde olduğu gibi doğrudan derleyici tarafından otomatik bir şekilde gerçekleştirilebilir ya da yazılım geliştirici tarafından ilgili bölümlerde referans sayma gerçekleştirilebilir.

Referans sayma deterministik bir çöp toplama performansı sergiler. Bunun nedeni program yürütülürken bir referansın oluşturulma, kopyalama ve serbest bırakılma noktaları her zaman sabit olduğundan referansın serbest bırakılma işlemi her zaman aynı algoritma adımında gerçekleşir.

Referans sayma için kullanılan sayma verisi çalışma zamanında ek bellek ayak izi anlamına gelir. Bununla birlikte referans sayma işlemi algoritmaya göre önemli miktarda çalışma zamanı yükü oluşturabilir. Bununla birlikte bazı problemlere sahiptir, bu problemlerin yok edilmesi amacıyla çeşitli yöntemler uygulanmaktadır ve bu yöntemler performansa olumsuz etki edebilmektedir.

Referans saymanın atomiklik (atomicity) sergilemesi gerekmektedir. Çok iş parçacıklı bir ortamda bir referansın aynı anda kopyalanması durumunda referans sayma doğru bir şekilde gerçekleşmeyebilir, bunu önlemek için referans sayma atomik bir şekilde gerçekleştirilmelidir. Bu atomiklik çok iş parçacıklı bir senaryo olmadığı durumlarda dahi küçük de olsa bir çalışma zamanı ek yükü oluşturur.

Referansın sayma verisi üstünde işlem gerektirecek herhangi bir durumun iş parçacıkları tarafından çok fazla tekrarlanması bu atomikliğin programın toplam çalışma süresinin önemli bir bölümünü oluşturmasına neden olabilir.

Referans saymanın diğer bir sorunu döngüselliktir. Örneğin iki referansın birbirlerine referans vermesi durumunda referans sayısının asla sıfırlanmadığı bir döngü oluşturulabilir, bu önlemek için çeşitli döngü algılama algoritmaları vardır.

Referans sayma çeşitli şekillerde uygulanabilir ve bu uygulamaların kendilerine has dezavantajları olabilir. Bazı referans sayma uygulamalarında referans sayma verisi sıfırlandığında tahsisin direkt serbest bırakılması yerine serbest bırakılmayı bekleyen tahsislerin bulunduğu bir bölümde listelenebilir ve periyodik olarak bu listedeki tahsisler serbest bırakılabilir.

Bir referans sayma verisinin sıfıra ulaşılması durumunda yok edilen tahsis de bir referans ise, bu sıralı bir referans sayma başlatabilir ve belirli durumlarda pek çok referansın sayma verisi sıfıra ulaştığından sıralı ve uzun bir serbest bırakmanın tetiklenmesine yol açabilir.
include
include
include

struct Car {
std::string brand;
std::string model;
};

int main() {
{
// ptr heap tahsisli bir Car sınıfı üstünde referans sayımı gerçekleştiriyor
// tahsis ptr ile birlikte 1 referansa sahip
std::shared_ptr ptr(new Car{"Ford", "GT40"});

{
// ptr2 artık ptr'nin tahsisine işaret ediyor
// tahsisin referans sayısı 2'ye ulaştı
std::shared_ptr ptr2{ ptr };

std::cout brand