Manajemen memori dalam JavaScript dilakukan secara otomatis dan tidak terlihat oleh kami. Kami membuat primitif, objek, fungsi… Semua itu membutuhkan memori.
Apa yang terjadi jika sesuatu tidak diperlukan lagi? Bagaimana cara mesin JavaScript menemukan dan membersihkannya?
Konsep utama manajemen memori dalam JavaScript adalah keterjangkauan .
Sederhananya, nilai-nilai yang “dapat dijangkau” adalah nilai-nilai yang dapat diakses atau digunakan. Mereka dijamin akan disimpan dalam memori.
Ada kumpulan nilai dasar yang secara inheren dapat dijangkau, yang tidak dapat dihapus karena alasan yang jelas.
Misalnya:
Nilai-nilai ini disebut akar .
Fungsi yang sedang dijalankan, variabel dan parameter lokalnya.
Fungsi lain pada rangkaian panggilan bersarang saat ini, variabel dan parameter lokalnya.
Variabel global.
(ada beberapa lainnya, yang internal juga)
Nilai lainnya dianggap dapat dijangkau jika dapat dijangkau dari akar melalui referensi atau rantai referensi.
Misalnya, jika ada objek dalam variabel global, dan objek tersebut memiliki properti yang mereferensikan objek lain, objek tersebut dianggap dapat dijangkau. Dan referensi yang dirujuknya juga dapat dijangkau. Contoh terperinci untuk diikuti.
Ada proses latar belakang di mesin JavaScript yang disebut pengumpul sampah. Ini memonitor semua objek dan menghapus objek yang tidak dapat dijangkau.
Berikut contoh paling sederhana:
// pengguna memiliki referensi ke objek tersebut biarkan pengguna = { nama: "Yohanes" };
Di sini panah menggambarkan suatu objek referensi. Variabel global "user"
mereferensikan objek {name: "John"}
(kami akan menyebutnya John agar singkatnya). Properti "name"
John menyimpan primitif, sehingga dilukis di dalam objek.
Jika nilai user
ditimpa, referensinya hilang:
pengguna = nol;
Sekarang John menjadi tidak bisa dijangkau. Tidak ada cara untuk mengaksesnya, tidak ada referensi ke sana. Pengumpul sampah akan membuang data dan mengosongkan memori.
Sekarang bayangkan kita menyalin referensi dari user
ke admin
:
// pengguna memiliki referensi ke objek tersebut biarkan pengguna = { nama: "Yohanes" }; biarkan admin = pengguna;
Sekarang jika kita melakukan hal yang sama:
pengguna = nol;
…Maka objek tersebut masih dapat dijangkau melalui variabel global admin
, sehingga harus tetap berada di memori. Jika kita menimpa admin
juga, maka bisa dihapus.
Sekarang contoh yang lebih kompleks. Keluarga:
fungsi menikah(pria, wanita) { wanita.suami = laki-laki; pria.istri = wanita; kembali { ayah : laki-laki, ibu: wanita } } biarkan keluarga = menikah({ nama: "Yohanes" }, { nama: "An" });
Fungsi marry
“mengawinkan” dua objek dengan memberinya referensi satu sama lain dan mengembalikan objek baru yang berisi keduanya.
Struktur memori yang dihasilkan:
Saat ini, semua objek dapat dijangkau.
Sekarang mari kita hapus dua referensi:
hapus keluarga.ayah; hapus keluarga.ibu.suami;
Tidaklah cukup hanya menghapus satu dari dua referensi ini, karena semua objek masih dapat dijangkau.
Namun jika kita menghapus keduanya, maka kita dapat melihat bahwa John tidak memiliki referensi masuk lagi:
Referensi keluar tidak penting. Hanya yang masuk yang dapat membuat suatu objek dapat dijangkau. Jadi, John sekarang tidak dapat dijangkau dan akan dihapus dari memori dengan semua datanya yang juga tidak dapat diakses.
Setelah pengumpulan sampah:
Ada kemungkinan bahwa seluruh pulau objek yang saling terkait menjadi tidak dapat dijangkau dan terhapus dari memori.
Objek sumbernya sama seperti di atas. Kemudian:
keluarga = nol;
Gambaran dalam memori menjadi:
Contoh ini menunjukkan betapa pentingnya konsep keterjangkauan.
Jelas sekali John dan Ann masih terhubung, keduanya memiliki referensi masuk. Tapi itu tidak cukup.
Objek "family"
sebelumnya telah diputuskan tautannya dari akarnya, tidak ada lagi referensi ke sana, sehingga seluruh pulau menjadi tidak dapat dijangkau dan akan dihapus.
Algoritma dasar pengumpulan sampah disebut “mark-and-sweep”.
Langkah-langkah “pengumpulan sampah” berikut dilakukan secara rutin:
Pengumpul sampah berakar dan “menandai” (mengingat) mereka.
Kemudian ia mengunjungi dan “menandai” semua referensi dari mereka.
Kemudian ia mengunjungi objek yang ditandai dan menandai referensinya . Semua objek yang dikunjungi akan diingat, agar tidak mengunjungi objek yang sama dua kali di kemudian hari.
…Demikian seterusnya hingga setiap referensi yang dapat dijangkau (dari akarnya) dikunjungi.
Semua objek kecuali yang ditandai akan dihapus.
Misalnya, struktur objek kita terlihat seperti ini:
Kita dapat dengan jelas melihat “pulau yang tidak dapat dijangkau” di sisi kanan. Sekarang mari kita lihat bagaimana pemulung “tandai dan sapu” menanganinya.
Langkah pertama menandai akarnya:
Kemudian kita ikuti referensinya dan tandai objek yang direferensikan:
…Dan terus ikuti referensi selanjutnya, selagi memungkinkan:
Sekarang objek yang tidak dapat dikunjungi dalam proses tersebut dianggap tidak dapat dijangkau dan akan dihapus:
Kita juga dapat membayangkan prosesnya seperti menumpahkan seember besar cat dari akarnya, yang mengalir melalui semua referensi dan menandai semua objek yang dapat dijangkau. Yang tidak ditandai kemudian dihapus.
Itulah konsep cara kerja pengumpulan sampah. Mesin JavaScript menerapkan banyak pengoptimalan untuk membuatnya berjalan lebih cepat dan tidak menimbulkan penundaan apa pun dalam eksekusi kode.
Beberapa optimasi:
Koleksi generasi – objek dibagi menjadi dua set: “yang baru” dan “yang lama”. Dalam kode umum, banyak objek yang umurnya pendek: mereka muncul, melakukan tugasnya, dan mati dengan cepat, jadi masuk akal untuk melacak objek baru dan menghapus memori dari objek tersebut jika itu masalahnya. Penyakit yang bertahan cukup lama akan menjadi “tua” dan lebih jarang diperiksa.
Pengumpulan tambahan – jika ada banyak objek, dan kami mencoba untuk berjalan dan menandai seluruh kumpulan objek sekaligus, mungkin diperlukan beberapa waktu dan menimbulkan penundaan yang terlihat dalam eksekusi. Jadi mesin membagi seluruh kumpulan objek yang ada menjadi beberapa bagian. Dan kemudian bersihkan bagian ini satu demi satu. Ada banyak kumpulan sampah kecil, bukan keseluruhan. Hal ini memerlukan pembukuan tambahan di antara mereka untuk melacak perubahan, namun kita mendapatkan banyak penundaan kecil, bukan penundaan besar.
Pengumpulan waktu menganggur – pengumpul sampah mencoba berjalan hanya saat CPU menganggur, untuk mengurangi kemungkinan efek pada eksekusi.
Terdapat optimasi dan variasi lain dari algoritma pengumpulan sampah. Meskipun saya ingin menjelaskannya di sini, saya harus menundanya, karena mesin yang berbeda menerapkan penyesuaian dan teknik yang berbeda. Dan, yang lebih penting lagi, banyak hal berubah seiring berkembangnya mesin, jadi mempelajari lebih dalam “sebelumnya”, tanpa kebutuhan nyata mungkin tidak ada gunanya. Kecuali, tentu saja, ini murni kepentingan, maka akan ada beberapa tautan untuk Anda di bawah ini.
Hal utama yang perlu diketahui:
Pengumpulan sampah dilakukan secara otomatis. Kita tidak bisa memaksa atau mencegahnya.
Objek disimpan dalam memori selama masih dapat dijangkau.
Direferensikan tidak sama dengan dapat dijangkau (dari akar): sekumpulan objek yang saling terkait bisa menjadi tidak dapat dijangkau secara keseluruhan, seperti yang kita lihat pada contoh di atas.
Mesin modern menerapkan algoritma pengumpulan sampah yang canggih.
Buku umum “Buku Pegangan Pengumpulan Sampah: Seni Manajemen Memori Otomatis” (R. Jones dkk) membahas beberapa di antaranya.
Jika Anda familiar dengan pemrograman tingkat rendah, informasi lebih detail tentang pengumpul sampah V8 ada di artikel Tur V8: Pengumpulan Sampah.
Blog V8 juga menerbitkan artikel tentang perubahan manajemen memori dari waktu ke waktu. Tentu saja, untuk mempelajari lebih lanjut tentang pengumpulan sampah, sebaiknya Anda mempersiapkan diri dengan mempelajari internal V8 secara umum dan membaca blog Vyacheslav Egorov yang bekerja sebagai salah satu insinyur V8. Saya berkata: “V8”, karena paling baik diliput oleh artikel di internet. Untuk mesin lain, banyak pendekatan yang serupa, namun pengumpulan sampah berbeda dalam banyak aspek.
Pengetahuan mendalam tentang mesin bagus ketika Anda membutuhkan optimasi tingkat rendah. Sebaiknya rencanakan hal itu sebagai langkah berikutnya setelah Anda memahami bahasa tersebut.