Sebagian besar pengembang jarang memikirkan tentang bagaimana manajemen memori JavaScript diimplementasikan. Mesin biasanya melakukan segalanya untuk pemrogram, jadi tidak masuk akal bagi pemrogram untuk memikirkan prinsip-prinsip mekanisme manajemen memori.
Namun cepat atau lambat, developer masih harus menghadapi masalah memori, seperti kebocoran. Yah, itu akan mungkin untuk menangani mereka hanya jika ada pemahaman tentang mekanisme alokasi memori. Artikel ini dikhususkan untuk penjelasan. Ini juga memberikan tip tentang jenis kebocoran memori yang paling umum di JavaScript.
Siklus hidup memori
Saat membuat fungsi, variabel, dll. di JavaScript, mesin mengalokasikan sejumlah memori. Kemudian dia membebaskannya - setelah memori tidak lagi diperlukan.
Sebenarnya, alokasi memori bisa disebut proses pemesanan memori dalam jumlah tertentu. Nah, rilisnya adalah pengembalian cadangan ke sistem. Anda dapat menggunakannya kembali sebanyak yang Anda suka.
Ketika variabel dideklarasikan atau fungsi dibuat, memori melewati loop berikutnya.
Di sini, di blok:
- Alokasi adalah alokasi memori yang dilakukan mesin. Ini mengalokasikan memori yang diperlukan untuk objek yang dibuat.
- Gunakan - penggunaan memori. Pengembang bertanggung jawab untuk saat ini, menulis kode untuk membaca dan menulis ke memori.
- Rilis - membebaskan memori. Di sinilah JavaScript kembali berfungsi. Setelah cadangan dilepaskan, memori dapat digunakan untuk tujuan lain juga.
"Objek" dalam konteks manajemen memori tidak hanya berarti objek JS, tetapi juga fungsi dan cakupan.
Tumpukan memori dan heap
Secara umum, semuanya tampak jelas - JavaScript mengalokasikan memori untuk semua yang pengembang tentukan dalam kode, dan kemudian, ketika semua operasi selesai, memori dibebaskan. Tapi di mana datanya disimpan?
Ada dua opsi - di tumpukan memori dan di heap. Apa yang pertama, apa yang kedua - nama struktur data yang digunakan oleh mesin untuk tujuan yang berbeda.
Stack adalah alokasi memori statis
Definisi stack dikenal oleh banyak orang. Merupakan struktur data yang digunakan untuk menyimpan data statis, ukurannya selalu diketahui pada saat kompilasi. JS telah menyertakan nilai primitif seperti string, number, boolean, undefined dan null, serta referensi fungsi dan objek.
Mesin "memahami" bahwa ukuran data tidak berubah, sehingga mengalokasikan jumlah memori tetap untuk setiap nilai. Proses pengalokasian memori sebelum eksekusi disebut alokasi memori statis.
Karena browser mengalokasikan memori terlebih dahulu untuk setiap tipe data, ada batasan ukuran data yang dapat diletakkan di sana. Karena browser mengalokasikan memori terlebih dahulu untuk setiap tipe data, ada batasan ukuran data yang dapat diletakkan di sana.
Heap - alokasi memori dinamis
Adapun heap, sama familiarnya dengan stack. Ini digunakan untuk menyimpan objek dan fungsi.
Tetapi tidak seperti stack, mesin tidak dapat "mengetahui" berapa banyak memori yang dibutuhkan untuk objek tertentu, sehingga memori dialokasikan sesuai kebutuhan. Dan cara pengalokasian memori ini disebut "dinamis" (alokasi memori dinamis).
Beberapa contoh
Komentar pada kode menunjukkan nuansa alokasi memori.
const person = {
name: 'John',
age: 24,
};
// JavaScript mengalokasikan memori untuk objek ini di heap.
// Nilainya sendiri primitif, jadi disimpan di stack.
const hobbies = ['hiking', 'reading'];
// Array adalah objek juga, jadi mereka pergi ke heap.
biarkan name = 'John'; // mengalokasikan memori untuk string
const age = 24; // mengalokasikan memori untuk nomor
name = 'John Doe'; // mengalokasikan memori untuk baris baru
const firstName = name.slice (0,4); // mengalokasikan memori untuk baris baru
// Nilai primitif secara inheren tidak dapat diubah: alih-alih mengubah nilai awal,
// JavaScript membuat yang lain.
Tautan JavaScript
Sedangkan untuk tumpukan, semua variabel mengarah ke sana. Jika nilainya bukan primitif, tumpukan berisi referensi ke objek heap.
Tidak ada urutan tertentu di dalamnya, yang berarti referensi ke area memori yang diinginkan disimpan di stack. Dalam situasi seperti ini, objek di heap tampak seperti bangunan, tetapi linknya adalah alamatnya.
JS menyimpan objek dan fungsi di heap. Tapi nilai dan referensi primitif ada di tumpukan.
Gambar ini menunjukkan organisasi penyimpanan nilai-nilai yang berbeda. Perhatikan bahwa orang dan newPerson menunjuk ke objek yang sama di sini.
Contoh
const person = {
name: 'John',
age: 24,
};
// Objek baru dibuat di heap, dan referensinya di stack.
Secara umum, tautan sangat penting dalam JavaScript.
Pengumpulan sampah
Sekarang adalah waktunya untuk kembali ke siklus hidup memori, yaitu pelepasannya.
Mesin JavaScript bertanggung jawab tidak hanya untuk alokasi memori, tetapi juga untuk deallocation. Dalam kasus ini, pengumpul sampah mengembalikan memori ke sistem.
Dan segera setelah mesin "melihat" bahwa variabel atau fungsi tidak lagi diperlukan, memori dibebaskan.
Tetapi ada masalah utama di sini. Faktanya adalah tidak mungkin untuk memutuskan apakah area memori tertentu diperlukan atau tidak. Tidak ada algoritma yang begitu akurat sehingga membebaskan memori secara real time.
Benar, hanya ada algoritme yang berfungsi dengan baik yang memungkinkan Anda melakukan ini. Mereka tidak sempurna, tetapi masih jauh lebih baik daripada banyak lainnya. Di bawah - cerita tentang pengumpulan sampah, yang didasarkan pada penghitungan referensi, serta tentang "algoritme penandaan".
Bagaimana dengan tautan?
Ini adalah algoritma yang sangat sederhana. Ini menghapus objek-objek yang tidak ada titik referensi lainnya. Berikut adalah contoh yang menjelaskannya dengan cukup baik.
Jika Anda telah menonton video, Anda mungkin memperhatikan bahwa hobi adalah satu-satunya objek di heap yang telah direferensikan di stack.
Siklus
Kerugian dari algoritma ini adalah tidak dapat memperhitungkan referensi melingkar. Mereka terjadi ketika satu atau lebih objek merujuk satu sama lain, di luar jangkauan dari sudut pandang kode.
let son = {
name: 'John',
};
let dad = {
name: 'Johnson',
}
son.dad = dad;
dad.son = son;
son = null;
dad = null;
Di sini putra dan ayah saling merujuk. Tidak ada akses ke objek untuk waktu yang lama, tetapi algoritme tidak melepaskan memori yang dialokasikan untuknya.
Justru karena algoritme menghitung referensi, menugaskan null ke objek tidak melakukan apa-apa, karena setiap objek masih memiliki referensi.
Algoritma untuk anotasi
Di sinilah algoritma lain datang untuk menyelamatkan, yang disebut metode mark and sweep. Algoritme ini tidak menghitung referensi, tetapi menentukan apakah objek yang berbeda dapat diakses melalui objek root. Di browser, ini adalah jendela, dan di Node.js, ini global.
Jika objek tidak tersedia, algoritme menandainya, lalu menghapusnya. Dalam kasus ini, objek root tidak pernah dimusnahkan. Masalah referensi siklik tidak relevan di sini - algoritme memungkinkan kita untuk memahami bahwa baik ayah maupun anak sudah tidak dapat diakses, sehingga mereka dapat "disapu" dan memori dikembalikan ke sistem.
Sejak tahun 2012, memang semua browser telah dilengkapi dengan pengumpul sampah yang bekerja persis sesuai dengan metode mark and sweep.
Bukan tanpa kekurangannya di sini.
Orang akan berpikir bahwa semuanya baik-baik saja, dan sekarang Anda bisa melupakan manajemen memori, menyerahkan semuanya pada algoritme. Tapi ternyata tidak demikian.
Penggunaan Memori yang Besar
Karena algoritme tidak mengetahui kapan memori tidak lagi diperlukan, aplikasi JavaScript dapat menggunakan lebih banyak memori daripada yang diperlukan. Dan hanya kolektor yang dapat memutuskan apakah akan membebaskan memori yang dialokasikan atau tidak.
JavaScript lebih baik dalam mengelola memori dalam bahasa tingkat rendah. Tetapi ada juga kerugian di sini, yang harus diingat. Secara khusus, JS tidak menyediakan alat manajemen memori, tidak seperti bahasa tingkat rendah, di mana pemrogram "secara manual" menangani alokasi dan pelepasan memori.
Performa
Memori tidak dihapus setiap saat baru dalam waktu. Pelepasan dilakukan secara berkala. Tetapi pengembang tidak dapat mengetahui secara pasti kapan proses ini dimulai.
Oleh karena itu, dalam beberapa kasus, pengumpulan sampah dapat berdampak negatif pada kinerja, karena algoritme memerlukan sumber daya tertentu agar dapat berfungsi. Benar, situasinya jarang menjadi sepenuhnya tidak terkendali. Paling sering, konsekuensi dari hal ini bersifat mikroskopis.
Kebocoran memori
Kebocoran memori adalah salah satu hal yang paling membuat frustrasi dalam pengembangan. Tetapi jika Anda mengetahui semua jenis kebocoran yang paling umum, Anda dapat mengatasi masalah tersebut tanpa banyak kesulitan.
Variabel Global
Kebocoran memori paling sering terjadi karena penyimpanan data dalam variabel global.
Di browser, jika Anda membuat kesalahan dan menggunakan var sebagai ganti const atau let, mesin akan melampirkan variabel ke objek jendela. Demikian pula, ia akan melakukan operasi pada fungsi yang ditentukan oleh kata fungsi.
user = getUser();
var secondUser = getUser();
function getUser() {
return 'user';
}
// Ketiga variabel - user, secondUser, dan
// getUser - akan dilampirkan ke objek jendela.
Ini hanya dapat dilakukan dengan fungsi dan variabel yang dideklarasikan dalam lingkup global. Anda dapat mengatasi masalah ini dengan menjalankan kode Anda dalam mode ketat.
Variabel global sering kali sengaja dideklarasikan; ini tidak selalu merupakan kesalahan. TAPI, bagaimanapun, kita tidak boleh lupa tentang membebaskan memori setelah data tidak lagi diperlukan. Untuk melakukan ini, Anda perlu menetapkan null ke variabel global.
window.users = null;
Callback dan timer
Aplikasi menggunakan lebih banyak memori daripada yang seharusnya, bahkan jika kita lupa tentang pengatur waktu dan callback. Masalah utamanya adalah aplikasi satu halaman (SPA), serta secara dinamis menambahkan callback dan penangan peristiwa.
Pengatur waktu yang terlupakan
const object = {};
const intervalId = setInterval(function() {
// , ,
//
doSomething(object);
}, 2000);
Fungsi ini berjalan setiap dua detik. Implementasinya seharusnya tidak ada habisnya. Masalahnya adalah bahwa objek yang memiliki referensi dalam interval tidak dimusnahkan hingga interval dihapus. Oleh karena itu, Anda perlu meresepkan secara tepat waktu:
clearInterval (intervalId);
Panggilan balik yang terlupa
Masalah bisa muncul jika penangan onClick dipasang ke tombol, dan tombol itu sendiri dihapus setelahnya - misalnya, tidak lagi diperlukan.
Sebelumnya, kebanyakan browser tidak bisa membebaskan memori yang dialokasikan untuk event handler seperti itu. Sekarang masalah ini adalah sesuatu dari masa lalu, tetapi tetap saja, meninggalkan penangan yang tidak lagi Anda butuhkan tidaklah sepadan.
const element = document.getElementById('button');
const onClick = () => alert('hi');
element.addEventListener('click', onClick);
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
Elemen DOM yang Terlupakan dalam Variabel
Ini mirip dengan kasus sebelumnya. Kesalahan terjadi ketika elemen DOM disimpan dalam variabel.
const elements = [];
const element = document.getElementById('button');
elements.push(element);
function removeAllElements() {
elements.forEach((item) => {
document.body.removeChild(document.getElementById(item.id))
});
}
Saat menghapus salah satu elemen di atas, Anda juga harus berhati-hati saat menghapusnya dari larik. Jika tidak, pengumpul sampah tidak akan secara otomatis membuangnya.
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);
});
}
Dengan menghapus elemen dari larik, Anda memperbarui isinya dengan daftar elemen di halaman.
Karena setiap elemen rumah memiliki referensi ke induknya, hal ini secara referensial mencegah pengumpul sampah membebaskan memori yang ditempati oleh induknya, yang menyebabkan kebocoran.
Di residu kering
Artikel ini menjelaskan mekanisme umum alokasi memori, serta penulis menunjukkan masalah apa yang dapat timbul dan bagaimana mengatasinya. Semua ini penting untuk setiap pengembang Java Script.
, Frontend- Skillbox:
, - ( SPA β Single Page Applications).
, β β β ( , ), , , .
β . .
, ( , , ). , , ββ β garbage collector.
- (js , garbage collectorβa). , .