Üretimde bir sekme üç saat sonra yavaşlıyor ama yerel ortamda hiç olmuyorsa, aradığınız yer kullanıcının makinesi değil; kodun içinde asılı kalmış bir referanstır. Bellek sızıntısı JavaScript'te çöp toplayıcının başarısızlığı değil, bizim çöpü bırakmayı reddetmemizdir. Toplayıcı yalnızca kendisine hiçbir yoldan ulaşılamayan nesneleri toplayabilir; siz bir nesneyi hâlâ işaret ediyorsanız, o nesne bellekte bir kiracı gibi oturmaya devam eder.
Sızıntının Dört Klasik Kapısı
İlk şüpheliler her projede aynıdır: kaldırılmayan olay dinleyicileri, kapatılmayan zamanlayıcılar, kapanış içinde tutulan büyük veriler ve global değişkenlere fazladan bağlanan nesneler. En sık gördüğümüz örnek şudur:
function sekmeyiBaslat() {
const rapor = buyukVeriSetiniYukle(); // 40 MB'lik dizi
return setInterval(function yaz() {
console.log(rapor.toplam);
}, 1000);
}
Bu zamanlayıcı durdurulmadığı sürece 40 MB'lik dizi hiç toplanamaz; sekme arka planda uyusa bile
bellek yer. Çözüm, clearInterval'ı bileşen sökülürken çağırmak ve bunu bir temizlik fonksiyonuna
bağlamaktır. Aynı hesap olay dinleyicileri için de geçerlidir: removeEventListener çağrılmayan
her kayıt, sökülmüş bir sayfada hayalet olarak yaşamaya devam eder. Kapanış içinde tutulan büyük
veri de aynı mekanizmayla korunur: fonksiyon döndükten sonra bile o değişkene işaret eden tek bir
referans kalmışsa, toplayıcı elini uzatamaz.
Performance Paneliyle Avlanmak
Chrome DevTools'un Memory paneli bu işin mikroskobudur. Heap snapshot al, normal gezin; ikinci ve üçüncü snapshot arasında büyüyen dizileri bul, karşılaştırma görünümünde "Objects allocated between" filtresiyle suçluyu yakala. Retention zincirini okuduğunda genellikle tek bir düğüm seni köke götürür: bir dinleyici, bir zamanlayıcı ya da temizlenmeyen bir önbellek.
const onbellek = new Map();
function onbellegeEkle(anahtar, deger) {
onbellek.set(anahtar, deger); // kimse temizlemiyor
}
Sınırsız büyüyen her Map, er ya da geç sızıntıya dönüşür. Çözüm ya kapasite sınırı koymak ya da
anahtarın yaşam döngüsünü izlemektir. Üretimde ise sayfa görünmez olduğunda visibilitychange
dinleyicisiyle ölçüm kaydedin; performance.memory en azından bir eğim verir. Ölçümü seyrek
örneklemlerle toplamak, izleme kodunun kendi kendine sızıntı üretmesini de önler.
Zayıf Referanslar
Modern çözüm WeakRef ve WeakMap'ten geçer: anahtar nesne yok olduğunda değer de toplanabilir
olur; önbellek anahtarlarını bu zayıf haritalarda tutmak çoğu sızıntıyı baştan önler. Ama bunlar
birer zarafettir; asıl çözüm çoğu zaman yaşam döngüsünü doğru yerde kapatmaktır. useEffect'te
dönüş fonksiyonu, disconnectedCallback ve yönlendirici geçişlerindeki temizlik hook'ları bu
savaşın asıl silahlarıdır. Kural basit: bir kaynak açtıysanız, kapanışının da adresini önceden
yazın. Kod incelemesinde sorulacak en iyi soru da budur: "bu nesne kimin ömrüne bağlı ve o ömür
bittiğinde referansı bırakacak bir satır var mı?" Bu soruyu her çekme isteğinde sormaya başlayan
ekiplerin üretimdeki bellek eğimleri bir yıl sonra belirgin biçimde düzleşir. Avcılık, refleks
değil rutin işidir; rutinleştirildiğinde de sızıntı avdan çıkmış olur. Son tuzak, aracın kendisine
aşırı güvenmektir: otomatik çözümleyiciler dinleyici ve zamanlayıcı sızıntılarının çoğunu bulur
ama kapanış içinde saklanan büyük veriyi yalnızca snapshot karşılaştırması ele verir. Araçları
çalıştırın, ama retention zincirini okumayı da öğrenin; sızıntı avcılığının özeti üç fiildir:
ölç, karşılaştır, bırak.
Okur Yorumları (0)