22 Kasım 2021

Çöp Toplama ( Garbage collection )

JavaScript dilinde hafıza yönetimi otomatik olarak gerçekleşir. Objeler, fonksiyonlar, değişkenler vs. hepsi hafızada yer alır.

Peki ya ihtiyaç yoksa ne yapılır? JavaScript motoru bunları nasıl temizler?

Erişilebilirlik

JavaScript’te hafıza yönetimi erişilebilirlik konsepti üzerinden olur.

Basit bir şekilde; erişilebilir değerler yararlıdır mantığı vardır. Bunlar kesinlikle hafızada yer alır.

  1. Başlangıçta varsayılan olarak var olan erişilebilir değerler bulunmaktadır bunlar hiçbir zaman silinemez.

    Örneğin:

    • O anda içinde bulunulan fonksiyonların yerel değişkenleri.
    • Birbirini çağıran fonksiyonların arasında gönderdikleri parametreler, değişkenler.
    • Global değişkenler.
    • (dahili değişkenler.)

    Bu değerlere kökler veya roots denir.

  2. Eğer kökten herhangi bir değişkene erişilebiliyorsa, bu zincirleme referanslarla veya referanslarla olabilir, bu durumda o değişken erişilebilirdir.

    Örneğin, yerel bir obje özellikleri içinde başka bir obje kullanırsa, o kullandığı obje de erişilibilir olmaktadır. Eğer referans verilen obje de başka bir objeye referans verirse o da erişilebilir olur. Detaylı bir örneği aşağıdaki gibidir.

JavaScript arka planda Çöp Toplama işlemini çalıştırır. Bu tüm erişelemeyen objeleri silme işini yapar.

Basit bir örnek

En basit örnek şu şekildedir:

// kullanici obje için referansa sahiptir.
let kullanici = {
  isim: "İhsan"
};

Bu görselde ok obje referansını gösterir. Global değişken olan "kullanici" {isim:"İhsan"} objesinin referansına sahiptir. Bu objenin "isim" özelliği ilkel bir tip tutar. Doğal olarak obje dışına referans verilmemiştir.

Eğer kullanici değerinin üstüne yazılırsa, bu referans kaybolur.

kullanici = null;

Şu anda İhsan ulaşılamaz oldu. Buna erişmenin bir yolu yok çünkü ona referans olan bir değişken yok. Bu durumda Çöp Toplama bunları hafızadan siler.

İki referans

Diyelim ki kullanici değişkeni kopyalandı yani referans kopyalandı.

// kullanici objeye referans olur
let kullanici = {
  isim: "İhsan"
};

let yonetici = kullanici;

Eğer bir önceki örneğin aynısı yapılırsa:

kullanici = null;

… Obje hala yonetici vasıtasıyla erişilebilir durumdadır. Öyleyse Çöp Toplama(Garbage Collector) bu objeyi silmeyecektir. Fakat yonetici değişkeninin de üzerine yazılırsa bu durumda objeye refans kalmayacağından hafızadan silinecektir.

Birbirine bağlı objeler.

Şimdiki örnek ise biraz daha karmaşık:

function evlilik(erkek, kadin) {
  kadin.bey = erkek;
  erkek.kadin = hanim;

  return {
    baba: erkek,
    anne: kadin
  }
}

let aile = evlilik({
  name: "İhsan"
}, {
  name: "Macide"
});

evlilik fonksiyonu verilen iki objeyi evlendirir ve bir obje yapar.

Son tahlilde hafıza haritası şu şekildedir:

Şu anda tüm objeler erişilebilir durumdadır.

İki referans silinirse:

delete aile.baba;
delete aile.anne.bey;

Bu referanslardan yalnız birisi sildiğinizde tüm objeler hala erişilebilir durumdadır.

Fakat ikisini birden silerseniz, İhsan’a erişilemez:

Dışarı giden referanslar önemli değildir. Sadece içeri gelenler o objeyi ulaşılabilir yapar. Öyleyse artık İhsan erişilemez ve hafızadan silinecektir. Ayrıca hiçbir verisine de erişilemez.

Çöp toplmaa işleminden sonra:

Erişilemez Ada

Biribirine bağlı objelerden bir bölümünün hafızadan tamamen silinmesi mümküdür.

Aşağıdaki örneğe bakarsanız:

aile = null;

Hafızadaki görüntüsü şu şekilde olur:

Bu örnek erişilebilirliğin ne kadar önemli bir konsept olduğunu gösterir.

İhsan ve Macide’nin hala birbirine bağlantısı vardır. Fakat bunlar sadece kendi aralarındadır ve yeterli değildir.

Önceki "aile" objesi ana kaynak’tan silinmiştir. Bundan dolayı artık obje içinde ne olursa olsun referanslarını kaybetmişlerdir.

Dahili Algoritmalar

Temelde Çöp Toplama algoritması “mark-and-sweep” algoritmasıdır.

Aşağıdaki Çöp Toplama işlemleri düzenli olarak yapılır:

  • Çöp toplayıcı kökleri işaretler(hatırlar).
  • Sonra bunlardan tüm referansları işaretler.
  • Sonrasında bunlardan objeleri ve objelerin referanslarını işaretler. Tüm erişilenler hatırlanır, bundan dolayı aynı objeyi ikinci defa ziyaret etmez.
  • … Bu şekilde ziyaret edilmemiş ziyaret edilmemiş referanslar bulunur.
  • İşaretlenmemiş olanlar silinir.

Diyelimki obje yapısı aşağıdaki gibi olsun:

“Ulaşılamayan ada” sağ tarafta açıkça görülebilir. “mark-and-sweep” adım adım şu şekilde çalışır:

İlk adım kökleri işaretlemek:

Sonra bunların referansları:

…Sonra eğer mümkün ise referansların referansları:

Son adım olarak ziyaret edilmeyen objeler “ulaşılamaz” addedilip silinir:

JavaScript motoru bunu hızlıca çalıştırmak ve kodun çalışmasını etkilememek için birçok optimizsyon yapar.

Bazı Optimizasyonlar:

  • Jenerason Koleksiyonu – objeler iki kümeye ayrılır: “yeni olanlar” ve “eski olanlar”. Çoğu obje birden var olur, işini hızlı bir şekilde yapar ve hızlıca ölür, bunların hemen silinmesi gerekir. Eğer silinemediler ise bu defa eskiler kümesine girerler ve daha az sıklıkla silinirler.

  • Artımlı Koleksiyon – Eğer çok fazla sayıda obje varsa bu objeleri bir seferde dolaşmak çok fazla zaman alacaktır. Kod çalışırken belki biraz yavaşlamaya neden olabilir. Bundan dolayı motorlar genelde çöp toplama işini bölümlere ayırırlar. Bu bölümleri ayrı ayrı çalışır. Tabi kendileri arasında bir şekilde değişiklikleri bildirmeleri gerekir, fakat bir tane büyük yerine bu şekilde küçük küçük işlemler hızı artırmaktadık.

  • Boş zaman Koleksiyonu – Çöp toplayıcı genelde CPU kullanılmadığı zamanlarda çalışır, böylece kodun çalışmasına olan etki en aza iner.

Bunlarla birlikte başka optimizasyon ve çöp toplama algoritmaları bulunmaktadır. Her motor kendine göre farklı optimizasyonu beraberinde getirir. Daha da önemlisi motor geliştikçe bakış açıları değişir. Analizler artar. Eğer gerçekten ilginizi çekiyorsa bu konu aşağıdaki linkler size yol gösterecektir.

Özet

Bilinmesi gereken temeller:

  • Çöp toplama otomatik olarak yapılır. Engellenemez veya korunulamaz.
  • Objeler erişilebilir olduğu müddetçe hafızada kalırlar.
  • Referans edilmiş olmak erişilebilir olmak değildir: birbiri içinde bağlantılı olan bir obje tamamen erişilemez hale getirilebilir.

Modern JavaScript motorları çöp toplama için çok gelişmiş algoritmalar kullanmaktadır.

Genel bir kitap önerisi olarak “The Garbage Collection Handbook: The Art of Automatic Memory Management” ( R.Jones et al) algoritmaların bazılarını kapsamaktadır.

Eğer alt seviye diller ile aranız iyi ise, daha derinlemesine bilgiyi aşağıdaki makaleden edinebilirsiniz: A tour of V8: Garbage Collection.

V8 blog Arada bir hafıza yönetimi hakkında belge yayınlamaktadır. Doğal olarak, çöp toplama hakkında bilgi sahibi olunmak isteniyorsa dahili yapıların bilinmesi gerekmektedir. Bu yapılar Vyacheslav Egorov takip edilerek öğrenilebilir. Kendisi “V8” motoru mühendislerindendir. "V8"in önerilmesinin nedeni internette hakkında çokça bilgi bulunabilmesinden dolayıdır. Diğer motorlar için çoğu yaklaşım benzerdir fakat çöp toplama birçok yönden farklılık gösterir.

Alt-seviye optimizasyonu istendiğinde derinlemesine bilgi sahibi olunması gerekmektedir. JavaScript dilini öğrendikten sonra bu yolda ilerlenmesi daha mantıklı olur.

Eğitim haritası