Ders 23 / 24
Bellek İncelemesi
Sızıntının erişilebilirlik üzerinden tanımı; sığ boyut, tutulan boyut ve tutucu yolu hesabı, anlık görüntülerin karşılaştırılması, güçlü ile zayıf başvurunun ölçülen farkı ve tarayıcıya özgü sızıntı kaynakları.
İçindekiler
Başarım kaydı bir anın dökümünü verir. Bazı sorunlar bu pencereye sığmaz: ölçüm sayfası açık kaldıkça yavaşlıyor, listeyi her yenileyişte biraz daha ağırlaşıyorsa, tek bir kayıtta görülecek bir uzun görev yoktur. Biriken şey zamanla artan ve serbest bırakılmayan bellektir.
Bu ders o birikimi ölçen aracı ve birikimin kaynağını bulma yolunu ele alır.
Sızıntının Tanımı: Erişilebilirlik
Eşzamansız JavaScript ve Çalışma Zamanı kursundaki Bellek Yaşam Döngüsü dersinde kurulan model burada tanının dayanağıdır: çöp toplayıcı, kök kümesinden erişilebilir olmayan her nesneyi geri alır. Erişilebilir olan hiçbir nesne toplanmaz.
Buradan sızıntının tanımı çıkar. Bellek sızıntısı, artık kullanılmayan bir nesnenin hâlâ erişilebilir kalmasıdır. Çöp toplayıcının hatası değildir; nesnenin kullanılmadığını bilemez, yalnızca erişilebilirliği bilir. Tanı bu yüzden “hangi nesne büyük” sorusuyla değil, “bu nesneye kim hâlâ başvuruyor” sorusuyla yapılır.
Sığ Boyut, Tutulan Boyut ve Tutucu Yolu
Bellek görünümlerinde bir nesne için iki ayrı boyut listelenir ve aralarındaki fark tanının merkezindedir.
// tutulan-boyut.mjs — sig boyut, tutulan boyut ve tutucu yolu hesabi const SIG = { kok: 16, dinleyici: 32, kapanis: 32, olcumDizisi: 8192, kopmusSatir: 128, hucre1: 64, hucre2: 64, pano: 64, rozet: 48, ortakBicem: 256, }; const KENAR = { kok: ["dinleyici", "pano"], dinleyici: ["kapanis"], kapanis: ["kopmusSatir", "olcumDizisi"], kopmusSatir: ["hucre1", "hucre2"], olcumDizisi: [], hucre1: [], hucre2: [], pano: ["rozet", "ortakBicem"], rozet: ["ortakBicem"], ortakBicem: [], }; const KOK_KUMESI = ["kok"]; function erisilebilir(haric = null) { const gorulen = new Set(); const yigit = KOK_KUMESI.filter((d) => d !== haric); while (yigit.length) { const d = yigit.pop(); if (gorulen.has(d)) continue; gorulen.add(d); for (const komsu of KENAR[d]) if (komsu !== haric) yigit.push(komsu); } return gorulen; } const boyut = (kume) => [...kume].reduce((t, d) => t + SIG[d], 0); function tutulanBoyut(dugum) { const oncesi = erisilebilir(); const sonrasi = erisilebilir(dugum); return boyut(oncesi) - boyut(sonrasi); } // Tutucu yolu: kokten dugume giden en kisa yol. function tutucuYolu(hedef) { const kuyruk = KOK_KUMESI.map((d) => [d]); const gorulen = new Set(KOK_KUMESI); while (kuyruk.length) { const yol = kuyruk.shift(); const son = yol[yol.length - 1]; if (son === hedef) return yol; for (const komsu of KENAR[son]) if (!gorulen.has(komsu)) { gorulen.add(komsu); kuyruk.push([...yol, komsu]); } } return null; } console.log("dugum".padEnd(14) + "sig".padStart(7) + "tutulan".padStart(9)); for (const d of Object.keys(SIG)) console.log(d.padEnd(14) + String(SIG[d]).padStart(7) + String(tutulanBoyut(d)).padStart(9)); console.log("\ntutucu yolu (kopmusSatir):", tutucuYolu("kopmusSatir").join(" → ")); console.log("tutucu yolu (ortakBicem) :", tutucuYolu("ortakBicem").join(" → "));
dugum sig tutulan kok 16 8896 dinleyici 32 8512 kapanis 32 8480 olcumDizisi 8192 8192 kopmusSatir 128 256 hucre1 64 64 hucre2 64 64 pano 64 368 rozet 48 48 ortakBicem 256 256 tutucu yolu (kopmusSatir): kok → dinleyici → kapanis → kopmusSatir tutucu yolu (ortakBicem) : kok → pano → ortakBicem
Sığ boyut, nesnenin kendi alanıdır. Tutulan boyut, o nesne serbest bırakıldığında geri kazanılacak toplam alandır — yani nesnenin ve yalnızca onun üzerinden erişilen her şeyin toplamı.
Dinleyici satırı ayrımı özetler: sığ boyutu otuz iki bayttır, tutulan boyutu sekiz binin üzerinde. Bir listede sığ boyuta göre sıralanan görünümde bu dinleyici hiç dikkat çekmez; tutulan boyuta göre sıralandığında listenin başına çıkar. Sızıntı avında sıralama ölçütü tutulan boyuttur.
rozet ile ortakBicem karşılaştırması ikinci kuralı verir. Rozetin tutulan boyutu
yalnızca kendi sığ boyutudur, çünkü işaret ettiği biçem nesnesine panodan da
başvurulmaktadır; rozet serbest bırakılsa o nesne yine erişilebilir kalır. Paylaşılan
nesneler hiçbir tutucunun tutulan boyutuna girmez; bu yüzden tutulan boyutların toplamı
öbeğin boyutunu vermez.
Tutucu yolu, kök kümesinden nesneye uzanan başvuru zinciridir ve sızıntının cevabıdır. Kopmuş satırın yolu, onu tutan şeyin bir kapanış, kapanışı tutan şeyin bir dinleyici olduğunu söyler; düzeltilecek yer bellekte değil, o dinleyicinin kaldırılmadığı koddadır.
Ölçmek: Anlık Görüntü ve Karşılaştırma
Tek bir öbek anlık görüntüsü bir andaki durumu verir; sızıntı ise bir eğilimdir. Bu yüzden ölçüm karşılaştırmayla yapılır.
Yerleşik yöntem üç görüntüye dayanır. Şüphelenilen işlem — ölçüm listesinin açılıp kapanması — bir kez yapılır ve birinci görüntü alınır; bu, ilk kullanımda oluşan ve normal sayılan yapıları temeline koyar. Aynı işlem birkaç kez daha tekrarlanır ve ikinci görüntü alınır. İşlem bir kez daha tekrarlanıp üçüncü görüntü alınır. İkinci ile üçüncü görüntü arasında var olmayı sürdüren nesneler sızıntı adaylarıdır; birinci görüntüye göre artan ama ikincide kaybolanlar geçici tahsislerdir.
Karşılaştırmanın anlamlı olması için görüntü almadan önce çöp toplamanın çalışmış olması gerekir; araçlar bunu görüntü alırken kendiliğinden yapar. Aksi hâlde ölçülen artış, henüz toplanmamış çöpten ibaret olabilir.
Güçlü ve Zayıf Başvuru
Sızıntı kaynaklarının en yaygını, nesneleri anahtar olarak saklayan yardımcı yapılardır: bir ögeye iliştirilmiş veriyi tutan bir eşleme, o öge ağaçtan çıksa bile ona başvurmayı sürdürür. Farkın ölçüsü doğrudan alınabilir.
Aşağıdaki ölçüm çöp toplayıcıyı elle çağırır ve bunun için node --expose-gc ile
çalıştırılmalıdır; bayrak verilmeden çalıştırıldığında çağrı tanımsızdır.
// sizinti-olcum.mjs — calistirma: node --expose-gc sizinti-olcum.mjs // Iki kayit defteri ayni isi yapar; farklari, anahtar olarak tuttuklari nesneye // guclu mu zayif mi basvurmalaridir. const MB = (bayt) => (bayt / 1024 / 1024).toFixed(1); function olc() { globalThis.gc(); // --expose-gc olmadan tanimsizdir return process.memoryUsage().heapUsed; } function dene(defter, sayi) { const taban = olc(); for (let i = 0; i < sayi; i++) { const dugum = { kimlik: i, olcum: new Array(64).fill(i) }; defter.set(dugum, { sonGuncelleme: i }); // dongu turu bitince dugum'e disaridan hicbir basvuru kalmaz } return olc() - taban; } const SAYI = 200_000; console.log(`${SAYI} dugum olusturulup birakildiktan sonra obekte kalan:`); console.log(" guclu basvurulu defter :", MB(dene(new Map(), SAYI)), "MB"); console.log(" zayif basvurulu defter :", MB(dene(new WeakMap(), SAYI)), "MB");
200000 dugum olusturulup birakildiktan sonra obekte kalan: guclu basvurulu defter : 127.5 MB zayif basvurulu defter : 1.0 MB
Sayılar çalışma zamanına ve makineye bağlıdır; oran ise kararlıdır. Güçlü başvurulu defter düğümlerin hepsini erişilebilir tutar ve hiçbiri toplanmaz. Zayıf başvurulu defterde anahtar, nesneyi hayatta tutmaz; başka başvurusu kalmayan düğüm toplanır ve onunla birlikte defterdeki girdi de gider.
Kural şöyle özetlenir: bir nesneye iliştirilmiş yan veri, o nesnenin ömrünü uzatmamalıdır. Zayıf başvuru bunu sağlayan yapıdır ve bedeli, defterin içeriğinin dolaşılamamasıdır — hangi anahtarların hâlâ var olduğu sorulamaz, çünkü soru sorulabilseydi anahtarlar hayatta tutulmuş olurdu.
Tarayıcıya Özgü Sızıntı Kaynakları
Sayfa bağlamında dört örüntü tekrarlanır ve dördü de tutucu yolunda tanınabilir izler bırakır.
Kopmuş düğümler. Ağaçtan çıkarılmış ama bir değişkenden, diziden ya da kapanıştan başvurulmayı sürdüren DOM düğümleri. Bellek görünümlerinde ayrı bir sınıf olarak işaretlenirler; bir kopmuş düğümün alt ağacının tamamı onunla birlikte tutulur. DOM API’si dersindeki durağan listelerin uzun süre saklanması bu sonucu üretir.
Kaldırılmayan dinleyiciler. Bağlanan her dinleyici hem işlevi hem de o işlevin kapanışını hedef düğüme bağlar. Olay Modeli dersinde tanıtılan iptal işareti seçeneği bu sorunu kaynağında çözer: tek bir iptalle o işarete bağlı bütün dinleyiciler kalkar. Özel öge yazan taraf için karşılığı, bağlanma geri çağrısında kurulanın bağlantı kesilme geri çağrısında sökülmesidir.
Duran zamanlayıcılar ve gözlemciler. Yinelenen bir zamanlayıcı, geri çağrısının kapanışını süresiz erişilebilir tutar. Aynısı, gözlemlediği öge ağaçtan çıksa bile durdurulmayan gözlemciler için geçerlidir. İkisinin de karşılığı açık bir durdurma çağrısıdır.
Sınırsız büyüyen önbellekler. Program içinde tutulan bir sonuç eşlemesi, sınır ve tahliye politikası verilmediğinde ömür boyu büyür. İşlemci Önbelleği dersinde tanımlanan tahliye politikası kavramı burada uygulanır: sınırsız önbellek, önbellek değil sızıntıdır.
Özet
- Bellek sızıntısı, kullanılmayan bir nesnenin erişilebilir kalmasıdır; tanı “kim başvuruyor” sorusuyla yapılır.
- Sığ boyut nesnenin kendi alanı, tutulan boyut serbest bırakıldığında geri kazanılacak toplam alandır; sızıntı avında sıralama ölçütü tutulan boyuttur.
- Paylaşılan nesneler hiçbir tutucunun tutulan boyutuna girmez; tutulan boyutların toplamı öbek boyutunu vermez.
- Tutucu yolu, kök kümesinden nesneye uzanan zincirdir ve düzeltilecek kod satırını gösterir.
- Sızıntı bir eğilimdir; ardışık anlık görüntülerde var olmayı sürdüren nesneler adaydır.
- Nesneye iliştirilen yan veri onun ömrünü uzatmamalıdır; zayıf başvurulu yapılar bunu sağlar, karşılığında içerikleri dolaşılamaz.
Sonraki Adım
Buraya kadarki araçlar sonucu ölçtü: hangi kural kazandı, istek ne kadar sürdü, hangi işlev ana iş parçacığını tuttu, hangi nesne bellekte kaldı. Hiçbiri “bu satır çalıştığında değişkenin değeri neydi” sorusunu yanıtlamaz. Ölçüm listesinin bir satırının neden eksik çizildiği, kaydın hangi koşulda yanlış dala girdiği, ancak programı seçilen bir noktada durdurup o andaki durumu okuyarak görülür. Sonraki ders bu duraklatma mekanizmasını ve üretim çıktısında durdurulan kodun kaynaktaki karşılığının nasıl bulunduğunu ele alır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.