Alokasi objek di Java terasa gratis — satu baris new, selesai. Padahal di balik layar, setiap objek menempuh perjalanan berjenjang melalui wilayah heap yang dirancang berdasarkan satu pengamatan statistik: mayoritas objek berumur sangat pendek. Iterator loop, StringBuilder penyusun teks, objek permintaan web — semuanya hidup beberapa milidetik lalu menjadi sampah. Para perancang JVM memanfaatkan pola ini dengan membagi heap menjadi generasi dan mengurus tiap generasi dengan strategi berbeda. Pada chapter ini kita ikuti perjalanan lengkap satu objek: lahir di Eden, berpeluang naik ke Survivor, dipromosikan ke Old, dan akhirnya dipungut.
Hypothesis Generasional #
Pengamatan “objek muda mati muda” dikenal sebagai generational hypothesis, dan data profil aplikasi khas mendukungnya berulang: sebagian besar alokasi menjadi sampah sebelum menginjak koleksi pertama. Dari sini heap generasional memperoleh bentuknya. Wilayah young generation — terdiri atas Eden dan dua Survivor space — menampung objek baru dan dikumpulkan sering dengan koleksi murah. Wilayah old generation menampung objek yang terbukti awet dan dikumpulkan lebih jarang.
Keuntungannya dua arah. Koleksi young cukup menyalin segelintir penyintas — bukan menyapu seluruh heap — sehingga cepat meski sering. Koleksi old memang lebih mahal, tetapi jarang terjadi karena hanya objek benar-benar awet yang sampai ke sana. Analoginya seperti sortir pos: kiriman singkat diselesaikan di meja depan, kiriman penyimpan jangka panjang dipindah ke gudang belakang sekali saja.
Lahir di Eden dengan TLAB #
Alokasi objek di Eden nyaris secepat menulis angka di kertas. Tiap thread memperoleh jalur alokasi privat bernama TLAB (Thread-Local Allocation Buffer) — blok kecil Eden milik satu thread. Menempatkan objek baru hanya menggeser penunjuk batas TLAB (bump-the-pointer) lalu menginisialisasi header dan field; tidak ada pencarian ruang bebas, tidak ada kunci antar thread. Inilah jawaban atas pertanyaan klasik “kenapa new di Java bisa lebih cepat daripada malloc di C”.
Objek yang tak muat di TLAB mengambil TLAB baru; objek raksasa — di G1, lebih besar dari separuh region — dialokasikan langsung di old generation sebagai humongous object. Ketika Eden penuh, koleksi muda (minor GC) dipicu: aplikasi berhenti sejenak, objek hidup di Eden dan Survivor aktif disalin ke Survivor cadangan, dan Eden dibersihkan utuh dengan sekali geser penunjuk.
Naik Kelas ke Survivor dan Old #
Objek yang menyintas minor GC tidak langsung berpindah ke old. Setiap objek membawa umur di headernya — bertambah satu tiap kali menyintas koleksi muda. Objek muda berpindah-pindah antara dua Survivor space (sebut saja S0 dan S1) yang bergantian peran: satu berisi data, satu lagi tujuan penyalinan. Pola bolak-balik ini menjaga salah satu space selalu kosong sehingga penyalinan tetap sederhana. Ketika umur objek melampaui ambang — diatur bendera -XX:MaxTenuringThreshold, secara adaptif hingga 15 pada collector generasional klasik — objek dipromosikan ke old generation. Perjalanan lengkapnya:
Wilayah old dikumpulkan oleh koleksi yang lebih berat: major GC yang menyasar old generation, dan full GC yang membersihkan seluruh heap termasuk Metaspace. Pada collector lawas keduanya stop-the-world penuh; pada G1 dan ZGC sebagian besar pekerjaan berjalan concurrent, menyisakan jeda pendek — detail komparasinya sudah kita bahas di Chapter Jenis Garbage Collector.
Log GC memperlihatkan momen koleksi dengan jujur. Bendera -Xlog:gc mengaktifkannya tanpa alat tambahan apa pun:
java -Xlog:gc -Xms32m -Xmx64m AlokasiCepat.java[0.002s][info][gc] Using G1
[0.367s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 15M->4M(34M) 4.503ms
selesai mengalokasikanBaris Pause Young (Normal) menandai minor GC; angka 15M->4M merangkum heap muda sebelum dan sesudah koleksi — dari 15 MB terpakai hanya 4 MB yang benar-benar hidup dan ikut disalin. Sisanya, lebih dari 70% ruang, adalah objek mati muda yang terbukti hipotesis generasional bekerja. Stempel waktu di awal baris tentu berbeda di setiap eksekusi, dan angka ukuran bergantung pada ukuran heap serta program.
Membuktikan dengan Program #
Bagaimana memperlihatkan umur objek dari dalam program? Sekali lagi kita pinjam jendela WeakReference dari Chapter Garbage Collection — kali ini untuk membandingkan nasib dua objek dengan pola penggunaan berbeda:
import java.lang.ref.WeakReference;
public class UmurObjek {
// objek yang direferensikan field static berumur sepanjang class
static final String PENDUDUK_TETAP = "data-inti";
record Sementara(int nomor) {}
public static void main(String[] args) {
for (int i = 1; i <= 2; i++) {
Sementara objek = new Sementara(i);
WeakReference<Sementara> pengamat = new WeakReference<>(objek);
System.out.println("objek ke-" + i + " sebelum gc : terlihat = " + (pengamat.get() != null));
objek = null; // penunjuk terakhir dilepas
System.gc();
System.out.println("objek ke-" + i + " setelah gc : "
+ (pengamat.get() != null ? "masih ada" : "dipungut"));
}
System.out.println("penduduk tetap sepanjang program: " + PENDUDUK_TETAP);
}
}Output yang Dihasilkan:
objek ke-1 sebelum gc : terlihat = true
objek ke-1 setelah gc : dipungut
objek ke-2 sebelum gc : terlihat = true
objek ke-2 setelah gc : dipungut
penduduk tetap sepanjang program: data-intiTiga pengamatan dari program ini:
- Baris
objek ke-N sebelum gc : terlihat = true: objek segar tentu saja terlihat — variabel lokalobjekadalah GC root yang menjaganya. - Baris
setelah gc : dipungut: begitu penunjuk terakhir dilepas, minor koleksi yang dipicuSystem.gc()cukup untuk memungutnya. Objek berumur pendek jarang sempat meninggalkan wilayah muda — persis skenario mayoritas alokasi di aplikasi nyata, dan persis alasan Eden didesain sebesar porsi heap muda. - Baris terakhir (
penduduk tetap sepanjang program: data-inti): kontras yang disengaja — objek yang terus dirujuk fieldstatic(atau literal string di constant pool) akan terus disalin antar generasi, naik umur, dan berakhir di old generation tanpa pernah menjadi kandidat pemungutan. Umur bukan penentu mati; reachability tetap satu-satunya hakim.
Perjalanan objek kini lengkap dari perspektif wilayah dan waktu. Namun kita meminjam WeakReference dua chapter berturut-turut sebagai jendela observasi — saatnya membuka kelasnya sendiri: empat kekuatan referensi yang memberi kita kendali halus atas umur objek, dari strong yang keras kepala hingga phantom yang baru bekerja setelah objek berakhir. Itulah pokok Chapter Strong, Weak, Soft, dan Phantom References.
Referensi #
- Oracle: Garbage Collection Tuning Guide (JDK 25) — Generations
- Oracle: java Tool Reference (JDK 25) — opsi -Xlog
- dev.java: Garbage Collection — Java Specifics
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
UmurObjek.java
Unduh berkasimport java.lang.ref.WeakReference;
public class UmurObjek {
// objek yang direferensikan field static berumur sepanjang class
static final String PENDUDUK_TETAP = "data-inti";
record Sementara(int nomor) {}
public static void main(String[] args) {
for (int i = 1; i <= 2; i++) {
Sementara objek = new Sementara(i);
WeakReference<Sementara> pengamat = new WeakReference<>(objek);
System.out.println("objek ke-" + i + " sebelum gc : terlihat = " + (pengamat.get() != null));
objek = null; // penunjuk terakhir dilepas
System.gc();
System.out.println("objek ke-" + i + " setelah gc : "
+ (pengamat.get() != null ? "masih ada" : "dipungut"));
}
System.out.println("penduduk tetap sepanjang program: " + PENDUDUK_TETAP);
}
}