Setiap object, array, dan string yang kita buat menempati memori. Di bahasa yang meminta pengembang mengatur memori secara manual, siklus ini terlihat jelas: minta ruang, pakai, lepas. JavaScript mengurus semuanya secara otomatis — tetapi “otomatis” hanya berarti kita tidak menulis perintahnya, bukan berarti aturannya tidak ada.
Memahami aturan itu penting karena performa ikut andal padanya: object yang masih dihitung “hidup” oleh runtime tetap memakan ruang, sekecil apa pun kode kita berjalan. Chapter ini membahas tiga pilar siklus memori — alokasi, reachability, dan garbage collection — lalu menyinggung kembali WeakMap dari section Collections sebagai alat yang bekerja seiring sistem ini.
Alokasi: Setiap Nilai Composite Meminta Ruang Baru #
Aturannya: setiap kali literal object atau array dievaluasi, runtime mengalokasikan ruang baru untuk nilainya. Loop yang membuat seribu object literal berarti seribu alokasi — walau isinya serupa, keduanya bukan object yang sama. Berkas berikut memeriksa siklus itu dari tiga sisi:
// siklus-memori.js — alokasi, reachability, dan referensi kuat vs lemah
// 1. alokasi: setiap literal object membuat object baru di memori
const keranjang = [];
for (let i = 1; i <= 5; i++) {
keranjang.push({ id: i, produk: "Buku " + i });
}
console.log("object dialokasikan :", keranjang.length);
// 2. reachability: dua nama menunjuk object yang sama
const alias = keranjang[0];
alias.produk = "Buku Tulis Sinar";
console.log("lewat array :", keranjang[0].produk);
console.log("lewat alias :", alias.produk);
// 3. memutus satu jalur: array tidak lagi menunjuk object itu
keranjang[0] = null;
console.log("alias masih memegang:", alias.produk);
// 4. referensi kuat (Map) vs referensi lemah (WeakMap)
const denganMap = new Map();
const denganWeak = new WeakMap();
let kunci = { id: 99 };
denganMap.set(kunci, "data sesi");
denganWeak.set(kunci, "data sesi");
const kunciSimpan = kunci; // nama kedua untuk object kunci yang sama
kunci = null; // variabel asli dilepas
console.log("Map menyimpan :", denganMap.get(kunciSimpan));
console.log("Map.size :", denganMap.size);
denganMap.delete(kunciSimpan); // entri kuat harus dilepas manual
console.log("Map setelah delete :", denganMap.size);
console.log("WeakMap.size :", denganWeak.size);Menjalankan node siklus-memori.js menghasilkan:
Output yang Dihasilkan:
object dialokasikan : 5
lewat array : Buku Tulis Sinar
lewat alias : Buku Tulis Sinar
alias masih memegang: Buku Tulis Sinar
Map menyimpan : data sesi
Map.size : 1
Map setelah delete : 0
WeakMap.size : undefinedMari kita telaah baris-baris pentingnya:
- Baris 4–7 (loop alokasi): lima kali iterasi menghasilkan lima alokasi object — angka
5di keluaran adalah jumlah object yang benar-benar dibuat, bukan salinan nilai. Inilah bentuk alokasi yang paling sering kita tulis tanpa sadar. - Baris 11–14 (dua nama, satu object):
aliasdankeranjang[0]menunjuk object yang sama; mengubahalias.produkterbaca dari dua sisi. Peristiwa ini disebut aliasing — dua jalur menuju satu nilai. - Baris 17–18 (memutus satu jalur): mengganti
keranjang[0]dengannullhanya melepas salah satu nama. Keluaranalias masih memegangmembuktikan object itu belum ke mana-mana: selama satu jalur tersisa, nilai tetap terjangkau.
Reachability: Kapan Object Masih Dihitung Hidup #
Aturan yang bekerja di balik layar: garbage collector tidak pernah menghapus object yang masih bisa dijangkau (reachable). Pemeriksaannya dimulai dari akar (roots) — variabel yang sedang hidup di scope aktif dan lingkup global — lalu menyusuri setiap referensi seperti peta jalan. Object yang punya setidaknya satu jalan dari akar ditandai hidup; object tanpa satu pun jalan menjadi kandidat dihapus.
Skenario pada berkas utama cukup untuk merasakan prosesnya. Setelah loop selesai, lima object terjangkau lewat keranjang. Baris 11 menambah jalan kedua untuk object pertama: sekarang alias juga menunjuknya. Baris 17 memutus jalur lewat array — tetapi jalur alias masih ada, dan keluaran membuktikannya. Barulah bila alias juga dilepas (misalnya dengan alias = null, di scope yang mengizinkan) tidak ada lagi jalan dari akar menuju object itu, dan garbage collector berhak mengangkatnya pada gilirannya.
Proses penandaan dan pengangkatan ini disebut garbage collection, dan strategi standar JavaScript berbentuk mark-and-sweep: tandai semua yang terjangkau, lalu sapu yang tersisa. Dua sifatnya perlu dipegang:
- Tidak terjadwal dari kode. Tidak ada API untuk memanggil GC pada saat yang kita pilih; runtime memutuskan sendiri waktunya — biasanya saat memori bertumbuh atau saat sistem menganggur. (Node menyediakan flag eksperimen
--expose-gcuntuk uji laboratorium, bukan untuk program harian.) - Tidak terlihat dari kode. Kita tidak bisa mencetak “object X sudah dihapus”. Yang bisa kita alami dari script hanyalah sisi reachability — persis seperti baris 17–18: selama ada jalur, data tetap terbaca.
Karena itu, cara praktis menjaga memori tetap ramping bukanlah “memanggil GC”, melainkan memastikan jalur referensi yang tidak dibutuhkan diputus: lepas dari array, ganti variabel dengan nilai baru, atau hapus entri collection.
Referensi Kuat vs Referensi Lemah #
Bagian keempat berkas membandingkan dua collection yang sikapnya berbeda terhadap object kunci. Map memegang kunci secara kuat (strong reference): selama Map itu hidup, kuncinya ikut terjangkau — entri harus dilepas dengan delete agar kuncinya bebas. WeakMap memegang kunci secara lemah (weak reference): ia tidak mencegah object kunci diangkat GC, dan begitu kunci tidak punya jalur lain, entri di dalam WeakMap menjadi sasaran pengangkatan berikutnya.
Keluaran memperlihatkan konsekuensi praktisnya. Map.size adalah 1 setelah variabel kunci aslinya dilepas — Map tetap menghitung dan menahan entrinya. Setelah delete, baru Map.size turun ke 0. Sementara WeakMap.size bernilai undefined: WeakMap bahkan tidak menawarkan penghitung, karena isinya bergantung pada jadwal GC yang tidak bisa kita amati dari script. Tidak ada forEach, tidak ada iterasi — satu-satunya akses adalah melalui kunci yang masih hidup.
Untuk mengingatkan perbedaan sikapnya dalam satu kalimat: Map memperpanjang umur kuncinya; WeakMap mengikuti umur kuncinya. Karena itu, pilihannya mengikuti pertanyaan sederhana — apakah entri sebaiknya hidup selama collection-nya hidup (pakai Map, dan delete saat selesai), ataukah sebaiknya hidup selama object pemiliknya dipakai (pakai WeakMap, tanpa pekerjaan pelepasan). WeakSet berperilaku sama dengan WeakMap untuk kasus tanpa nilai: sekadar menandai object yang ikut siklus hidup pemiliknya, misalnya daftar node yang sudah diberi penanda proses.
Contoh keputusan yang nyata: catatan posisi gulir per elemen dalam aplikasi satu halaman. Elemen dibuat dan dihapus sepanjang sesi; catatannya hanya bernilai selama elemennya ada. Menyimpannya di Map berarti setiap elemen yang dihapus tetap ditahan oleh catatan — persis pola kebocoran yang dibahas chapter berikutnya. Menyimpannya di WeakMap membuat pelepasan jadi otomatis, mengikuti siklus hidup elemennya.
Alur pikir di chapter ini adalah sisi sehatnya: referensi yang disengaja. Masalah baru muncul ketika jalur referensi tersisa tanpa kita sadari — listener yang tak dilepas, timer yang tak dihentikan, closure yang menahan data besar. Itulah topik chapter berikutnya: kebocoran memori.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
siklus-memori.js
Unduh berkas// siklus-memori.js — alokasi, reachability, dan referensi kuat vs lemah
// 1. alokasi: setiap literal object membuat object baru di memori
const keranjang = [];
for (let i = 1; i <= 5; i++) {
keranjang.push({ id: i, produk: "Buku " + i });
}
console.log("object dialokasikan :", keranjang.length);
// 2. reachability: dua nama menunjuk object yang sama
const alias = keranjang[0];
alias.produk = "Buku Tulis Sinar";
console.log("lewat array :", keranjang[0].produk);
console.log("lewat alias :", alias.produk);
// 3. memutus satu jalur: array tidak lagi menunjuk object itu
keranjang[0] = null;
console.log("alias masih memegang:", alias.produk);
// 4. referensi kuat (Map) vs referensi lemah (WeakMap)
const denganMap = new Map();
const denganWeak = new WeakMap();
let kunci = { id: 99 };
denganMap.set(kunci, "data sesi");
denganWeak.set(kunci, "data sesi");
const kunciSimpan = kunci; // nama kedua untuk object kunci yang sama
kunci = null; // variabel asli dilepas
console.log("Map menyimpan :", denganMap.get(kunciSimpan));
console.log("Map.size :", denganMap.size);
denganMap.delete(kunciSimpan); // entri kuat harus dilepas manual
console.log("Map setelah delete :", denganMap.size);
console.log("WeakMap.size :", denganWeak.size);