Bahasa seperti C menyerahkan urusan memori ke programmer: alokasikan, lalu lepaskan sendiri. Java memilih jalan sebaliknya — kita membuat objek sebebas-bebasnya dengan new, dan garbage collector (GC) yang memutuskan kapan objek benar-benar tidak dipakai lagi lalu memungut areanya untuk dipakai ulang. Pertanyaan kuncinya bukan “bagaimana memungut”, melainkan “bagaimana tahu objek sudah matangu” — dan jawaban JVM mungkin berbeda dari dugaan banyak orang. Pada chapter ini kita bedah cara GC menentukan hidup dan mati sebuah objek, membuktikannya dengan dua program observasi, lalu melihat algoritma dasar di balik proses pemungutan.
Menentukan Hidup dari Akar #
GC tidak menghitung berapa banyak referensi yang menunjuk sebuah objek — teknik reference counting itu rapuh karena dua objek dapat saling menunjuk membentuk siklus yang tak pernah habis hitungannya. Sebagai gantinya, JVM menjalankan reachability analysis: dimulai dari sekumpulan GC roots — titik yang pasti hidup — lalu menelusuri seluruh rantai referensi. Objek yang terjangkau dari akar dinyatakan hidup; sisanya, apa pun jumlah penunjuknya, adalah sampah. GC roots mencakup antara lain:
- variabel lokal dan parameter pada frame method yang sedang berjalan di semua thread,
- field
staticdari class yang sudah terinisialisasi, - objek yang terdaftar di JNI dan referensi khusus seperti
Threadyang masih hidup.
Konsekuensinya sederhana namun kuat: objek tidak perlu “dihapus” secara eksplisit. Cukup pastikan tidak ada lagi jalan menuju objek tersebut — variabel lokal habis umur saat method selesai, field static habis umur saat class di-unload — dan GC akan menganggapnya sampah pada koleksi berikutnya.
Membuktikan dengan Program #
Bagaimana kita mengamati keputusan GC, yang memang tidak mencetak apa pun secara bawaan? Triknya adalah jendela observasi berupa WeakReference — pembungkus referensi yang tidak dihitung sebagai penunjuk hidup, sehingga get()-nya mengembalikan null tepat ketika objek yang dibungkusnya sudah dipungut. Program pertama menguji akar berupa variabel lokal:
import java.lang.ref.WeakReference;
public class PengamatRoot {
static WeakReference<StringBuilder> pengamat;
public static void main(String[] args) {
buatData();
System.gc();
if (pengamat.get() == null) {
System.out.println("setelah frame hilang + gc(): objek dipungut");
} else {
System.out.println("setelah frame hilang + gc(): objek masih ada");
}
}
static void buatData() {
StringBuilder lokal = new StringBuilder("data sementara");
pengamat = new WeakReference<>(lokal);
System.out.println("di dalam method : objek terlihat, isi = " + pengamat.get());
}
}Output yang Dihasilkan:
di dalam method : objek terlihat, isi = data sementara
setelah frame hilang + gc(): objek dipungutMari kita telaah alurnya:
- Baris 18-19 (
buatDatamemanggilnew StringBuilder): objek lahir di heap, ditunjuk variabel lokallokal— variabel ini adalah GC root selama framebuatDatamasih hidup. - Baris 20 (
pengamat = new WeakReference<>(lokal)): jendela observasi dipasang. KarenaWeakReferencebukan penunjuk hidup, hidup-matinya objek kini sepenuhnya bergantung padalokal. - Baris 12 (
buatData()selesai): frame method dibongkar — variabellokalikut mati, dan seluruh jalur ke objek terputus. PemanggilanSystem.gc()lalu memicu koleksi, dan baris terakhir output menegaskan objek sudah dipungut.
Siklus Bukan Penghalang #
Bukti kedua sekaligus bantahan terhadap bayangan reference counting: dua objek yang saling menunjuk erat tetap dipungut begitu penunjuk dari luar hilang. Dengan penghitungan referensi, pasangan ini tidak akan pernah terkumpulkan — hitungan masing-masing selalu satu. Dengan reachability analysis, keduanya sampah karena tidak ada akar yang menjangkaunya:
import java.lang.ref.WeakReference;
public class SiklusTerungut {
static class Node {
Node tetangga;
}
public static void main(String[] args) {
Node a = new Node();
Node b = new Node();
a.tetangga = b; // a menunjuk b
b.tetangga = a; // b menunjuk a: terbentuk siklus
WeakReference<Node> pengamatA = new WeakReference<>(a);
WeakReference<Node> pengamatB = new WeakReference<>(b);
a = null; // kedua referensi luar dilepas
b = null; // objek saling menunjuk satu sama lain
System.gc();
System.out.println("pengamatA.get() = " + pengamatA.get());
System.out.println("pengamatB.get() = " + pengamatB.get());
}
}Output yang Dihasilkan:
pengamatA.get() = null
pengamatB.get() = nullDua baris null pada output adalah kesimpulan utuhnya: saling menunjuk tidak menyelamatkan objek dari pemungutan, karena penelusuran dari GC roots tidak pernah sampai ke keduanya setelah a dan b di-nol-kan. Inilah alasan struktur data berjejaring — peta, graph, cache bersilang — tidak perlu pembongkaran manual di Java.
Mark, Sweep, dan Compact #
Setelah objek hidup diketahui, bagaimana areanya direbut kembali? Algoritma dasarnya bekerja tiga fase. Mark: GC menandai seluruh objek yang terjangkau dari roots, biasanya dengan mengeser bit penanda di header objek. Sweep: area objek tak bertanda dinyatakan bebas dan dikembalikan ke daftar ruang kosong. Compact: objek hidup digeser merapat ke satu sisi sehingga ruang bebas menyatu — mencegah fragmentasi dan menyederhanakan alokasi berikutnya menjadi sekadar menggeser penunjuk.
Fase mark dan compact menghentikan seluruh thread aplikasi Java sejenak — momen stop-the-world — karena penandaan tidak boleh kejar-kejaran dengan objek yang berpindah. Karena itu ukuran dan frekuensi jeda menjadi ukuran mutu sebuah collector, dan inilah pembeda antara Serial, Parallel, G1, hingga ZGC yang akan kita bandingkan pada Chapter Jenis Garbage Collector. Sebagian collector modern memperkecil jeda dengan menandai secara concurrent bersama thread aplikasi yang terus berjalan.
Mati Proses Bukan Urusan GC #
Sebagai catatan penutup yang kerap disalahpahami: garbage collector adalah urusan selama program hidup — memakai ulang memori agar aplikasi bisa berjalan lama tanpa membengkak. Ketika proses Java berakhir, sistem operasi melepas seluruh memori proses tersebut sekaligus, selesai atau tidak GC sempat bekerja. Maka mematikan aplikasi lewat kill -9 tidak meninggalkan “sampah” di mesin — yang hilang hanyalah kesempatan shutdown gracefully: menyiram data ke disk, menutup koneksi, dan menjalankan hook Runtime.addShutdownHook. Sebaliknya, kebocoran memori di aplikasi berumur panjang tetap merupakan bencana nyata meski proses akan dimatikan akhirnya — pokok bahasan Chapter Memory Leaks.
Peta konsep GC kini lengkap: akar penentu hidup, algoritma pemungutan, dan jeda stop-the-world. Chapter berikutnya memperkenalkan empat collector HotSpot yang mengimplementasikan konsep-konsep ini dengan karakter jeda dan throughput yang berbeda-beda — dan kriteria memilihnya.
Referensi #
- Oracle: Garbage Collection Tuning Guide (JDK 25)
- dev.java: Garbage Collection — Java Specifics
- Oracle: API System.gc() (Java SE 25)
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
PengamatRoot.java
Unduh berkasimport java.lang.ref.WeakReference;
public class PengamatRoot {
static WeakReference<StringBuilder> pengamat;
public static void main(String[] args) {
buatData();
System.gc();
if (pengamat.get() == null) {
System.out.println("setelah frame hilang + gc(): objek dipungut");
} else {
System.out.println("setelah frame hilang + gc(): objek masih ada");
}
}
static void buatData() {
StringBuilder lokal = new StringBuilder("data sementara");
pengamat = new WeakReference<>(lokal);
System.out.println("di dalam method : objek terlihat, isi = " + pengamat.get());
}
}
SiklusTerungut.java
Unduh berkasimport java.lang.ref.WeakReference;
public class SiklusTerungut {
static class Node {
Node tetangga;
}
public static void main(String[] args) {
Node a = new Node();
Node b = new Node();
a.tetangga = b; // a menunjuk b
b.tetangga = a; // b menunjuk a: terbentuk siklus
WeakReference<Node> pengamatA = new WeakReference<>(a);
WeakReference<Node> pengamatB = new WeakReference<>(b);
a = null; // kedua referensi luar dilepas
b = null; // objek saling menunjuk satu sama lain
System.gc();
System.out.println("pengamatA.get() = " + pengamatA.get());
System.out.println("pengamatB.get() = " + pengamatB.get());
}
}