Ders 14 / 17
Çöp Toplama
Erişilebilirlik temelli toplamanın işleyişi, başvuru saymanın çevrimlerde başarısız oluşu, kuşak varsayımı, artımlı toplamanın duraklamaları ve zayıf başvuru yapıları.
İçindekiler
Önceki ders toplayıcıyı kara kutu olarak kullandı: “erişilemeyeni toplar” denildi. Bu ders kutuyu açar. Amaç toplayıcı yazmak değil, davranışını öngörebilmektir — özellikle duraklamaların nereden geldiğini ve zayıf başvuruların hangi boşluğu doldurduğunu.
İşaretle ve Süpür
Temel yöntem iki aşamalıdır. İşaretleme aşamasında kök kümesinden başlanır ve başvurular izlenerek ulaşılan her nesne işaretlenir. Süpürme aşamasında işaretlenmemiş nesnelerin yeri geri alınır.
Bu, Veri Yapıları kursundaki çizge dolaşımının doğrudan uygulanmasıdır: nesneler düğüm, başvurular kenardır; işaretleme, köklerden başlayan bir erişilebilirlik dolaşımıdır. Ziyaret işaretlemesi de aynı işi görür — daha önce görülen nesne yeniden dolaşılmaz.
Yöntemin belirleyici özelliği şudur: maliyeti ölü nesne sayısına değil, yaşayan nesne sayısına bağlıdır. Ölü nesneler hiç ziyaret edilmez; yalnız yerleri geri alınır.
Başvuru Sayma Neden Yetmez
Alternatif bir yöntem, her nesnede kendisine kaç başvuru olduğunu saymaktır. Sayı sıfıra düştüğünde nesne serbest bırakılır. Bu yöntem hızlıdır ve duraklama üretmez; ama bir sınıfı hiç toplayamaz: birbirine başvuran nesneler.
const toplananlar = []; const kayitDefteri = new FinalizationRegistry((ad) => { toplananlar.push(ad); }); function cevrimKur() { const a = { ad: "A1 düğümü" }; const b = { ad: "B2 düğümü" }; a.komsu = b; b.komsu = a; kayitDefteri.register(a, "çevrimdeki A1"); kayitDefteri.register(b, "çevrimdeki B2"); console.log("çevrim kuruldu:", a.komsu.ad, "<->", b.komsu.ad); } cevrimKur(); global.gc(); await new Promise((karar) => setTimeout(karar, 10)); global.gc(); await new Promise((karar) => setTimeout(karar, 10)); console.log("toplananlar:", toplananlar.sort().join(", "));
$ node --expose-gc cevrim.mjs çevrim kuruldu: B2 düğümü <-> A1 düğümü toplananlar: çevrimdeki A1, çevrimdeki B2
Toplama sırası öngörülemez olduğu için adlar biriktirilip sıralanarak yazdırıldı; bu, bir sonraki dersteki uyarının erken bir örneğidir — toplayıcının hangi nesneyi topladığı gözlenebilir, hangi sırayla topladığı gözleme dayandırılamaz.
İki nesnenin her birine bir başvuru vardı ve bu başvurular hiç sıfırlanmadı; buna karşın ikisi de toplandı. Nedeni açıktır: kök kümesinden hiçbirine ulaşılamıyordu. Erişilebilirlik ölçütü, “kaç başvuru var” sorusunu değil “köklerden ulaşılıyor mu” sorusunu sorar ve çevrimler bu soruyu doğru yanıtlar.
Ayrım pratikte sık karşılaşılan bir yapı için önemlidir: birbirine başvuran iki nesne — üst ve alt öğe, gözlemci ve gözlenen, iki uçlu bir liste düğümü — JavaScript’te sızıntı kaynağı değildir. Sızıntı, çevrimden değil kökten gelir.
Kuşak Varsayımı
Ölçümler tutarlı bir gözlem verir: nesnelerin çoğu kısa yaşar. Bir fonksiyonun içinde üretilen ara nesneler hemen ölür; uygulama boyunca yaşayan yapılar ise azdır.
Toplayıcılar bu gözlemi kullanır. Öbek en az iki bölgeye ayrılır: yeni nesnelerin ayrıldığı genç bölge ve toplamayı atlatan nesnelerin taşındığı yaşlı bölge. Genç bölge sık ve hızlı taranır; yaşlı bölge seyrek taranır.
Sonucu, sık yapılan bir mikro eniyilemenin gereksizliğidir: kısa ömürlü nesne üretmekten kaçınmak çoğu durumda kazanç getirmez, çünkü toplayıcı zaten bu duruma göre ayarlanmıştır. Asıl maliyet, yaşlı bölgeye geçen ve orada gereksiz yere kalan nesnelerdir.
Duraklamalar
İşaretleme sırasında nesne çizgesi değişmemelidir; aksi halde işaretleme tutarsız olur. Bu nedenle toplayıcı çalışırken program durur — tek iş parçacıklı bir ortamda bunun anlamı, olay döngüsünün de durmasıdır.
Duraklamayı küçültmek için iki teknik kullanılır. Artımlı toplama, işaretlemeyi küçük parçalara böler ve aralarında programa sıra verir. Eşzamanlı toplama, işaretlemenin bir bölümünü ayrı bir iş parçacığında yürütür. İkisi birlikte, uzun tek bir duraklamayı çok sayıda kısa duraklamaya dönüştürür.
Uygulama açısından çıkarım şudur: çöp toplama bir başarım kalemidir ve ana iş parçacığında görünür. Bir sonraki dersteki tanı yöntemleri ve profilleme, toplama duraklamalarını da kapsar.
Zayıf Başvurular
Bazı durumlarda bir nesneye başvurmak ama onu yaşatmamak istenir: bir önbellek, bir kimlik eşlemesi, bir gözlemci kaydı. Güçlü başvuru bu iş için yanlıştır; nesne artık kullanılmasa bile önbellekte durduğu için toplanamaz.
Zayıf yapılar bu boşluğu doldurur. WeakMap ve WeakSet, anahtarlarını yaşatmaz.
const kayitDefteri = new FinalizationRegistry((ad) => { console.log("toplandı:", ad); }); const gucluHarita = new Map(); const zayifHarita = new WeakMap(); function gucluTut() { const anahtar = { istasyon: "A1" }; gucluHarita.set(anahtar, "güçlü haritadaki değer"); kayitDefteri.register(anahtar, "güçlü haritanın anahtarı"); } function zayifTut() { const anahtar = { istasyon: "B2" }; zayifHarita.set(anahtar, "zayıf haritadaki değer"); kayitDefteri.register(anahtar, "zayıf haritanın anahtarı"); } gucluTut(); zayifTut(); global.gc(); await new Promise((karar) => setTimeout(karar, 10)); global.gc(); await new Promise((karar) => setTimeout(karar, 10)); console.log("güçlü haritadaki giriş sayısı:", gucluHarita.size); console.log("toplama turu bitti");
$ node --expose-gc zayif.mjs toplandı: zayıf haritanın anahtarı güçlü haritadaki giriş sayısı: 1 toplama turu bitti
İki anahtar da fonksiyonlar döndükten sonra erişilemez hale geldi. Zayıf haritadaki anahtar toplandı; güçlü haritadaki anahtar toplanmadı, çünkü haritanın kendisi ona güçlü bir başvuru tutuyordu ve harita da köklerden erişilebilirdi.
Buradan çıkan kural doğrudandır: bir eşlemenin ömrü anahtar nesnenin ömrüne bağlıysa zayıf yapı kullanılır. Ölçüm akışında istasyon nesnesi başına tutulan yardımcı veri buna örnektir; istasyon nesnesi düştüğünde yardımcı veri de düşmelidir.
Zayıf yapıların üç sınırı vardır. Anahtarları yalnız nesne olabilir. Üzerlerinde gezinme
yapılamaz, boyutları sorulamaz — çünkü içerik toplayıcının kararına bağlı olarak değişir
ve bu gözlemlenebilir olsaydı program belirlenimci olmazdı. WeakRef ile tekil bir zayıf
başvuru da tutulabilir; ancak deref çağrısının ne zaman boş döneceği öngörülemez, bu
yüzden program mantığı buna dayandırılmaz.
Kuşak Varsayımını Ölçmek
Kuşak varsayımının doğrudan gözlenebilir bir izi vardır: kısa ömürlü nesnelerden oluşan bir döngü, yığın kullanımını kalıcı olarak büyütmez. Ölçüm mutlak sayılarla yapılamaz — değerler çalıştırmaya, sürüme ve makineye göre değişir — ama ilişki sınanabilir.
function yigin() { const b = process.memoryUsage(); return { heapUsed: Math.round(b.heapUsed / 1024) }; } const once = yigin(); for (let i = 0; i < 200000; i++) { const gecici = { sensor: "S-01", deger: i }; if (gecici.deger < 0) console.log("olmaz"); } const sonra = yigin(); global.gc(); const toplamaSonrasi = yigin(); console.log("kısa ömürlü döngü öncesi (KiB):", once.heapUsed > 0); console.log("toplama sonrası yığın, döngü sonrasından küçük mü:", toplamaSonrasi.heapUsed <= sonra.heapUsed);
$ node --expose-gc kusak.mjs kısa ömürlü döngü öncesi (KiB): true toplama sonrası yığın, döngü sonrasından küçük mü: true
İki yüz bin nesne yaratıldı ve hiçbiri döngüden sonra erişilebilir değil. Toplama çağrıldığında yığın kullanımı geri düşer; genç kuşakta ölen nesneler için ödenen bedel, yaşayanların kopyalanmasıyla sınırlıdır.
Sayı yerine karşılaştırma yazdırmak bilinçlidir: mutlak bayt değerleri her çalıştırmada farklıdır ve derse yazıldığında yanıltıcı olur. Ölçülebilir olan, kullanımın yönüdür.
Toplayıcının Vaat Etmedikleri
Üç noktanın açıkça bilinmesi gerekir.
Toplama anı öngörülemez. “Erişilemez oldu” ile “bellek geri verildi” arasındaki süre çalışma zamanının kararıdır.
Sonlandırıcı geri çağrılar güvence değildir. Program sonlanırken hiç çalışmayabilir; bu nedenle dosya, bağlantı ve zamanlayıcı gibi kaynakların temizliği açık çağrılarla yapılır.
Toplayıcı sızıntıyı çözmez. Erişilebilir bir nesne, gereksiz olsa bile toplanmaz. Sızıntı bir toplayıcı kusuru değil, bir program kusurudur — ve bu nedenle tanısı programda aranır.
Özet
- İşaretle-süpür yöntemi köklerden başlayan bir erişilebilirlik dolaşımıdır; maliyeti yaşayan nesne sayısına bağlıdır.
- Başvuru sayma çevrimleri toplayamaz; erişilebilirlik ölçütü toplar, bu yüzden karşılıklı başvuran nesneler JavaScript’te sızıntı kaynağı değildir.
- Kuşak varsayımı, nesnelerin çoğunun kısa yaşadığı gözlemine dayanır ve genç bölgenin sık taranmasını sağlar.
- Toplama ana iş parçacığını durdurur; artımlı ve eşzamanlı teknikler uzun duraklamayı kısa duraklamalara böler.
- Zayıf yapılar anahtarlarını yaşatmaz; ömrü başka bir nesnenin ömrüne bağlı eşlemeler için kullanılır.
Sonraki Adım
Toplayıcının nasıl çalıştığı bilindiğine göre, çalışmadığı durumlar tanınabilir hale gelir. Sonraki ders sızıntıyı tanımlar ve ölçer: eşzamansız kodda en sık görülen dört sızıntı biçimi, öbek anlık görüntüsünün ne içerdiği ve iki anlık görüntüyü karşılaştırarak hangi nesnelerin biriktiğini sayma yöntemi. Ölçüm akışının sızdıran ve sızdırmayan iki sürümü aynı ölçütle karşılaştırılacak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.