Saya telah mencatat bug Chrome berikut , yang menyebabkan banyak kebocoran memori yang serius dan tidak jelas dalam kode saya:
(Hasil ini menggunakan profil memori Chrome Dev Tools ' , yang menjalankan GC, dan kemudian mengambil snapshot tumpukan dari segala sesuatu yang tidak dikumpulkan dikumpulkan.)
Dalam kode di bawah ini, someClass
contohnya adalah sampah yang dikumpulkan (baik):
var someClass = function() {};
function f() {
var some = new someClass();
return function() {};
}
window.f_ = f();
Tapi itu tidak akan menjadi sampah yang dikumpulkan dalam kasus ini (buruk):
var someClass = function() {};
function f() {
var some = new someClass();
function unreachable() { some; }
return function() {};
}
window.f_ = f();
Dan tangkapan layar yang sesuai:
Tampaknya penutupan (dalam hal ini, function() {}
) membuat semua benda "hidup" jika objek direferensikan oleh penutupan lain dalam konteks yang sama, apakah penutupan itu sendiri bahkan dapat dijangkau.
Pertanyaan saya adalah tentang pengumpulan penutupan sampah di browser lain (IE 9+ dan Firefox). Saya cukup akrab dengan alat-alat webkit, seperti JavaScript heap profiler, tapi saya tahu sedikit alat peramban lain, jadi saya belum bisa mengujinya.
Di mana dari tiga kasus ini akankah sampah IE9 + dan Firefox mengumpulkan someClass
contoh?
unreachable
fungsi tidak pernah dieksekusi sehingga tidak ada yang benar-benar dicatat.Jawaban:
Sejauh yang saya tahu, ini bukan bug tetapi perilaku yang diharapkan.
Dari halaman Manajemen memori Mozilla : "Pada 2012, semua browser modern mengirimkan pengumpul sampah tanda dan sapuan." "Batasan: objek harus dibuat secara eksplisit tidak dapat dijangkau " .
Dalam contoh Anda di mana kegagalan
some
itu masih dapat dijangkau di penutupan. Saya mencoba dua cara untuk membuatnya tidak terjangkau dan keduanya berfungsi. Entah Anda mengatursome=null
kapan Anda tidak membutuhkannya lagi, atau Anda menetapkanwindow.f_ = null;
dan itu akan hilang.Memperbarui
Saya sudah mencobanya di Chrome 30, FF25, Opera 12 dan IE10 di Windows.
The standar tidak mengatakan apa-apa tentang pengumpulan sampah, tetapi memberikan beberapa petunjuk tentang apa yang harus terjadi.
Jadi, suatu fungsi akan memiliki akses ke lingkungan induknya.
Jadi,
some
harus tersedia di penutupan fungsi kembali.Lalu mengapa tidak selalu tersedia?
Tampaknya Chrome dan FF cukup pintar untuk menghilangkan variabel dalam beberapa kasus, tetapi di Opera dan IE,
some
variabel tersedia di penutupan (NB: untuk melihat ini atur breakpointreturn null
dan periksa debugger).GC dapat ditingkatkan untuk mendeteksi apakah
some
digunakan atau tidak dalam fungsi, tetapi akan rumit.Contoh buruk:
Dalam contoh di atas GC tidak memiliki cara untuk mengetahui apakah variabel tersebut digunakan atau tidak (kode diuji dan berfungsi di Chrome30, FF25, Opera 12 dan IE10).
Memori dilepaskan jika referensi ke objek rusak dengan menetapkan nilai lain
window.f_
.Menurut saya ini bukan bug.
sumber
setTimeout()
callback berjalan, fungsi lingkupsetTimeout()
callback dilakukan dan seluruh lingkup harus dikumpulkan, melepaskan referensi kesome
. Tidak ada lagi kode yang dapat dijalankan yang dapat mencapai instancesome
dalam penutupan. Itu harus menjadi sampah yang dikumpulkan. Contoh terakhir bahkan lebih buruk karenaunreachable()
bahkan tidak dipanggil dan tidak ada yang memiliki referensi untuk itu. Lingkupnya juga harus GCed. Keduanya tampak seperti bug. Tidak ada persyaratan bahasa di JS untuk "membebaskan" hal-hal dalam lingkup fungsi.f()
dipanggil, tidak ada referensi aktualsome
lagi. Itu tidak bisa dijangkau dan harus GCed.eval
ini kasus yang sangat spesial. Misalnya,eval
tidak bisa alias ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ), misvar eval2 = eval
. Jikaeval
digunakan (dan karena itu tidak dapat dipanggil dengan nama yang berbeda, itu mudah dilakukan), maka kita harus berasumsi bahwa itu dapat menggunakan apa pun dalam ruang lingkup.Saya menguji ini di IE9 + dan Firefox.
Situs langsung di sini .
Saya berharap untuk berakhir dengan array 500
function() {}
's, menggunakan memori minimal.Sayangnya, bukan itu masalahnya. Setiap fungsi kosong berpegang pada array sejuta angka (tidak dapat dijangkau, tetapi tidak GC'ed).
Chrome akhirnya berhenti dan mati, Firefox menyelesaikan semuanya setelah menggunakan hampir 4GB RAM, dan IE tumbuh lebih lambat secara asimptot hingga menunjukkan "Kehabisan memori".
Menghapus salah satu dari baris yang dikomentari akan memperbaiki semuanya.
Tampaknya ketiga peramban ini (Chrome, Firefox, dan IE) menyimpan catatan lingkungan per konteks, bukan per penutupan. Boris berhipotesis bahwa alasan di balik keputusan ini adalah kinerja, dan itu tampaknya mungkin, meskipun saya tidak yakin bagaimana performannya dapat disebut dalam terang percobaan di atas.
Jika perlu referensi penutupan
some
(diberikan saya tidak menggunakannya di sini, tapi bayangkan saya lakukan), jika bukansaya menggunakan
itu akan memperbaiki masalah memori dengan memindahkan penutup ke konteks yang berbeda dari fungsi saya yang lain.
Ini akan membuat hidup saya jauh lebih membosankan.
PS Karena penasaran, saya mencoba ini di Jawa (menggunakan kemampuannya untuk mendefinisikan kelas di dalam fungsi). GC berfungsi seperti yang semula saya harapkan untuk Javascript.
sumber
Heuristik berbeda-beda, tetapi cara umum untuk menerapkan hal semacam ini adalah dengan membuat catatan lingkungan untuk setiap panggilan ke
f()
dalam kasus Anda, dan hanya menyimpan penduduk lokalf
yang benar-benar ditutup (dengan beberapa penutupan) dalam catatan lingkungan tersebut. Lalu setiap penutupan dibuat dalam panggilan kef
menjaga catatan lingkungan hidup. Saya percaya ini adalah bagaimana Firefox mengimplementasikan penutupan, setidaknya.Ini memiliki manfaat akses cepat ke variabel tertutup dan kesederhanaan implementasi. Ini memiliki kelemahan dari efek yang diamati, di mana penutupan jangka pendek menutup beberapa variabel menyebabkannya tetap hidup dengan penutupan berumur panjang.
Seseorang dapat mencoba membuat beberapa catatan lingkungan untuk penutupan yang berbeda, tergantung pada apa yang sebenarnya mereka tutup, tetapi itu bisa menjadi sangat rumit dengan sangat cepat dan dapat menyebabkan masalah kinerja dan memori ...
sumber
O(n^2)
atauO(2^n)
sebagai ledakan, tetapi bukan peningkatan yang proporsional.seperti add (5); // mengembalikan 5
tambah (20); // mengembalikan 25 (5 + 20)
tambahkan (3); // mengembalikan 28 (25 + 3)
dua cara Anda dapat melakukan ini pertama adalah mendefinisikan variabel global. Tentu saja, Anda dapat menggunakan variabel global untuk menahan total. Tetapi perlu diingat bahwa pria ini akan memakan Anda hidup-hidup jika Anda (ab) menggunakan global.
sekarang cara terbaru menggunakan penutupan tanpa mendefinisikan variabel global
sumber
sumber
sumber