Selamat siang teman!
Dalam sebagian besar kasus, sebagai pengembang JavaScript, kita tidak perlu khawatir tentang bekerja dengan memori. Mesin melakukannya untuk kita.
Namun, suatu saat Anda akan mengalami masalah yang disebut "kebocoran memori", yang hanya dapat diselesaikan dengan mengetahui bagaimana memori dialokasikan dalam JavaScript.
Dalam artikel ini, saya akan menjelaskan cara kerja alokasi memori dan pengumpulan sampah, dan cara menghindari beberapa masalah umum yang terkait dengan kebocoran memori.
Siklus hidup memori
Saat Anda membuat variabel atau fungsi, mesin JavaScript mengalokasikan memori untuknya dan melepaskannya saat tidak lagi diperlukan.
Mengalokasikan memori adalah proses mencadangkan ruang tertentu dalam memori, dan membebaskan memori adalah membebaskan ruang tersebut sehingga dapat digunakan untuk tujuan lain.
Setiap kali variabel atau fungsi dibuat, memori melewati tahapan berikut:
- Alokasi memori - mesin secara otomatis mengalokasikan memori untuk objek yang dibuat
- Penggunaan memori - membaca dan menulis data ke memori tidak lebih dari menulis dan membaca data dari variabel
- Membebaskan memori - langkah ini juga dilakukan secara otomatis oleh mesin. Setelah memori dibebaskan, itu dapat digunakan untuk tujuan lain.
Heap dan stack
Pertanyaan selanjutnya adalah: apa artinya ingatan? Di mana sebenarnya data disimpan?
Mesin memiliki dua tempat seperti itu: heap dan stack. Heap dan Stack adalah struktur data yang digunakan oleh mesin untuk tujuan yang berbeda.
Stack: alokasi memori statis
Semua data dalam contoh ini disimpan di stack karena bersifat primitif,
stack adalah struktur data yang digunakan untuk menyimpan data statis. Data statis adalah data yang ukurannya diketahui mesin pada tahap kompilasi kode. Dalam JavaScript, data semacam itu bersifat primitif (string, angka, boolean, tidak terdefinisi, dan null) dan referensi yang mengarah ke objek dan fungsi.
Karena mesin mengetahui bahwa ukuran data tidak akan berubah, mesin mengalokasikan ukuran memori tetap untuk setiap nilai. Proses pengalokasian memori sebelum menjalankan kode disebut alokasi memori statis. Karena mesin mengalokasikan ukuran memori tetap, ada batasan tertentu pada ukuran ini, yang sangat bergantung pada browser.
Heap: alokasi memori dinamis
Heap untuk menyimpan objek dan fungsi. Berbeda dengan tumpukan, mesin tidak mengalokasikan ukuran memori tetap untuk objek. Memori dialokasikan sesuai kebutuhan. Alokasi memori ini disebut dinamis. Berikut adalah tabel perbandingan kecil:
| Tumpukan | Tumpukan |
|---|---|
| Nilai dan referensi primitif | Objek dan fungsi |
| Ukurannya diketahui pada waktu kompilasi | Ukurannya diketahui saat runtime |
| Memori tetap dialokasikan | Ukuran memori untuk setiap objek tidak dibatasi |
Contoh dari
Mari kita lihat beberapa contoh.
const person = {
name: "John",
age: 24,
};
Mesin mengalokasikan memori untuk objek ini di heap. Namun, nilai properti disimpan di tumpukan.
const hobbies = ["hiking", "reading"];
Array adalah objek, jadi disimpan di heap
let name = "John";
const age = 24;
name = "John Doe";
const firstName = name.slice(0, 4);
Primitif tidak bisa diubah. Ini berarti bahwa alih-alih mengubah nilai asli, JavaScript membuat yang baru.
Tautan
Semua variabel disimpan di tumpukan. Dalam kasus nilai non-primitif, tumpukan menyimpan referensi ke objek di heap. Memori di heap tidak teratur. Inilah mengapa kami membutuhkan tautan di tumpukan. Anda dapat menganggap tautan sebagai alamat dan objek sebagai rumah di alamat tertentu.
Pada gambar di atas, kita dapat melihat bagaimana berbagai nilai disimpan. Perhatikan bahwa orang dan orang baru menunjuk ke objek yang sama
Contoh dari
const person = {
name: "John",
age: 24,
};
Ini membuat objek baru di heap dan referensi ke objek tersebut di tumpukan
Pengumpulan sampah
Segera setelah mesin mengetahui bahwa sebuah variabel atau fungsi tidak lagi digunakan, ini akan membebaskan memori yang ditempati.
Faktanya, masalah membebaskan memori yang tidak terpakai tidak dapat dipecahkan: tidak ada algoritma yang sempurna untuk menyelesaikannya.
Pada artikel ini, kita akan melihat dua algoritme yang menawarkan solusi terbaik hingga saat ini: referensi penghitungan pengumpulan sampah serta tandai dan sapu.
Pengumpulan sampah melalui penghitungan referensi
Semuanya sederhana di sini - objek yang tidak ada titik referensi yang dihapus dari memori. Mari kita lihat contohnya. Garis mewakili tautan.
Perhatikan bahwa hanya objek "hobbies" yang tersisa di heap, karena hanya objek ini yang direferensikan di stack.
Tautan siklik
Masalah dengan metode pengumpulan sampah ini adalah ketidakmampuan untuk menentukan referensi melingkar. Ini adalah situasi dimana dua atau lebih objek menunjuk satu sama lain tetapi tidak memiliki xrefs. Itu. benda-benda ini tidak dapat diakses dari luar.
const son = {
name: "John",
};
const dad = {
name: "Johnson",
};
son.dad = dad;
dad.son = son;
son = null;
dad = null;
Karena objek "son" dan "dad" merujuk satu sama lain, algoritme penghitungan referensi tidak akan dapat membebaskan memori. Namun, objek ini tidak lagi tersedia untuk kode eksternal.
Algoritma untuk penandaan dan pembersihan
Algoritma ini memecahkan masalah referensi melingkar. Alih-alih menghitung referensi yang mengarah ke suatu objek, ini menentukan ketersediaan objek dari objek root. Objek root adalah objek "jendela" di browser atau objek "global" di Node.js.
Algoritme menandai objek sebagai tidak terjangkau dan menghapusnya. Dengan demikian, referensi melingkar tidak lagi menjadi masalah. Dalam contoh di atas, objek "ayah" dan "anak" tidak dapat dijangkau dari objek root. Mereka akan ditandai sebagai sampah dan dibuang. Algoritme yang dipermasalahkan telah diterapkan di semua browser modern sejak 2012. Perbaikan yang dilakukan sejak saat itu adalah tentang implementasi dan peningkatan kinerja, tetapi bukan ide inti dari algoritme.
Kompromi
Pengumpulan sampah otomatis memungkinkan kita untuk fokus pada membangun aplikasi dan tidak membuang waktu pada manajemen memori. Namun, semuanya ada harganya.
Penggunaan memori
Mengingat bahwa algoritme memerlukan beberapa waktu untuk menentukan bahwa memori tidak lagi digunakan, aplikasi JavaScript cenderung menggunakan lebih banyak memori daripada yang sebenarnya mereka butuhkan.
Meskipun objek tersebut ditandai sebagai sampah, pengumpul harus memutuskan kapan akan mengumpulkannya agar tidak menghalangi aliran program. Jika Anda ingin aplikasi Anda menjadi seefisien mungkin dalam hal penggunaan memori, lebih baik Anda menggunakan bahasa pemrograman tingkat rendah. Namun perlu diingat bahwa bahasa-bahasa ini memiliki kompromi sendiri.
Performa
Algoritme pengumpulan sampah dijalankan secara berkala untuk membersihkan objek yang tidak digunakan. Masalahnya adalah kita sebagai developer tidak tahu persis kapan ini akan terjadi. Pengumpulan sampah dalam jumlah besar atau pengumpulan sampah yang sering dapat memengaruhi kinerja karena memerlukan daya pemrosesan dalam jumlah tertentu. Namun, ini biasanya terjadi tanpa disadari oleh pengguna dan pengembang.
Kebocoran memori
Mari kita lihat sekilas masalah kebocoran memori yang paling umum.
Variabel Global
Jika Anda mendeklarasikan variabel tanpa menggunakan salah satu kata kunci (var, let, atau const), variabel tersebut menjadi properti objek global.
users = getUsers();
Menjalankan kode Anda dalam mode ketat menghindari hal ini.
Terkadang kami sengaja mendeklarasikan variabel global. Dalam kasus ini, untuk membebaskan memori yang ditempati oleh variabel seperti itu, Anda harus memberikan nilai "null":
window.users = null;
Timer dan panggilan balik yang terlupakan
Jika Anda lupa tentang pengatur waktu dan callback, penggunaan memori aplikasi Anda dapat meningkat secara dramatis. Hati-hati, terutama saat membuat aplikasi satu halaman (SPA) di mana penangan peristiwa dan callback ditambahkan secara dinamis.
Pengatur waktu yang terlupakan
const object = {};
const intervalId = setInterval(function () {
// , , ,
// ,
doSomething(object);
}, 2000);
Kode di atas menjalankan fungsinya setiap 2 detik. Jika Anda tidak lagi membutuhkan pengatur waktu, Anda harus membatalkannya dengan:
clearInterval(intervalId);
Ini sangat penting untuk SPA. Meskipun Anda membuka halaman lain di mana pengatur waktu tidak digunakan, pengatur waktu akan berjalan di latar belakang.
Panggilan balik yang terlupakan
Misalkan Anda mendaftarkan penangan untuk klik tombol yang nanti Anda hapus. Nyatanya, ini tidak lagi menjadi masalah, tetapi tetap disarankan untuk menghapus penangan yang tidak lagi dibutuhkan:
const element = document.getElementById("button");
const onClick = () => alert("hi");
element.addEventListener("click", onClick);
element.removeEventListener("click", onClick);
element.parentNode.removeChild(element);
Tautan di luar DOM
Kebocoran memori ini mirip dengan yang sebelumnya, ini terjadi saat menyimpan elemen DOM di JavaScript:
const elements = [];
const element = document.getElementById("button");
elements.push(element);
function removeAllElements() {
elements.forEach((item) => {
document.body.removeChild(document.getElementById(item.id));
});
}
Jika Anda menghapus salah satu elemen ini, Anda juga harus menghapusnya dari array. Jika tidak, item tersebut tidak dapat dibuang oleh pengumpul sampah:
const elements = [];
const element = document.getElementById("button");
elements.push(element);
function removeAllElements() {
elements.forEach((item, index) => {
document.body.removeChild(document.getElementById(item.id));
elements.splice(index, 1);
});
}
Saya harap Anda menemukan sesuatu yang menarik untuk diri Anda sendiri. Terima kasih atas perhatian Anda.