Chapter sebelumnya menetapkan konsep pemungutan: akar, penandaan, jeda stop-the-world. Namun “garbage collector” bukan satu implementasi — HotSpot membawa empat collector pilihan yang mengimplementasikan konsep itu dengan kompromi berbeda: ada yang memaksimalkan throughput dengan jeda panjang, ada yang mengejar jeda sub-milidetik dengan jejak memori lebih besar. JDK 25 mengaktifkan G1 secara bawaan, tetapi pilihan lain tetap relevan untuk beban kerja tertentu. Pada chapter ini kita bandingkan keempatnya, mendeteksi collector aktif lewat program, lalu menetapkan kriteria pemilihan berdasar karakter aplikasi.
Empat Kolektor Bawaan JDK #
Tabel berikut merangkum keempat collector yang tersedia di HotSpot JDK 25 beserta profil khasnya:
| Collector | Bendera aktifasi | Jeda khas | Throughput | Jejak memori | Pasangan alami |
|---|---|---|---|---|---|
| Serial | -XX:+UseSerialGC | panjang, satu thread | moderat | paling kecil | container mini, mesin 1 CPU |
| Parallel | -XX:+UseParallelGC | panjang, banyak thread | tertinggi | sedang | batch, pemrosesan data |
| G1 | -XX:+UseG1GC | pendek dan terkendali | tinggi | sedang | server umum (bawaan) |
| ZGC | -XX:+UseZGC | sub-milidetik | tinggi | besar | layanan latensi rendah |
Serial menjalankan seluruh fase GC dengan satu thread — jeda terpanjang, tetapi tanpa overhead koordinasi antar thread dan struktur data pendukung, sehingga paling hemat memori. Parallel memperkerjakan banyak thread untuk fase mark dan compact sehingga total waktu GC memendek; total jeda tetap ada dan panjang, namun waktu produktif aplikasi (throughput) paling tinggi. Keduanya memakai model generasional klasik yang akan kita telusuri di Chapter Object Lifecycle di JVM.
Melihat Kolektor Aktif #
Setiap collector mengelola heap dengan nama pool yang khas — G1 memakai G1 Eden Space, Parallel memakai awalan PS, Serial memakai nama generasi klasik, ZGC menampilkan pool generasinya sendiri. Nama pool bisa dibaca lewat API java.lang.management, dan polanya cukup khas untuk mengidentifikasi collector:
import java.lang.management.ManagementFactory;
import java.util.List;
public class DeteksiKolektor {
public static void main(String[] args) {
List<String> pool = ManagementFactory.getMemoryPoolMXBeans().stream()
.map(b -> b.getName())
.filter(n -> n.contains("Eden") || n.contains("Old")
|| n.contains("Tenured") || n.contains("Young"))
.sorted()
.toList();
String kolektor;
if (pool.stream().anyMatch(n -> n.startsWith("G1"))) {
kolektor = "G1";
} else if (pool.stream().anyMatch(n -> n.startsWith("PS"))) {
kolektor = "Parallel";
} else if (pool.stream().anyMatch(n -> n.startsWith("ZGC"))) {
kolektor = "ZGC";
} else if (pool.contains("Tenured Gen")) {
kolektor = "Serial";
} else {
kolektor = "tidak dikenali";
}
System.out.println("kolektor aktif : " + kolektor);
pool.forEach(n -> System.out.println(" pool: " + n));
}
}Output yang Dihasilkan:
kolektor aktif : G1
pool: G1 Eden Space
pool: G1 Old GenProgram ini mendeteksi G1 pada JVM tanpa bendera apa pun — bukti bahwa G1 adalah collector bawaan JDK 25. Jalankan ulang dengan bendera collector lain dan hasilnya berubah seketika:
java -XX:+UseSerialGC DeteksiKolektor.java
java -XX:+UseParallelGC DeteksiKolektor.java
java -XX:+UseZGC DeteksiKolektor.javakolektor aktif : Serial
pool: Eden Space
pool: Tenured Genkolektor aktif : ZGC
pool: ZGC Old Generation
pool: ZGC Young GenerationMari kita telaah cara kerja deteksinya:
- Baris 9-11 (filter stream): dari seluruh pool JVM, program hanya menyimpan pool generasional — kata
Eden,Old,Tenured, atauYoungdi namanya. Pool Metaspace dan CodeHeap tersaring karena tidak relevan untuk identifikasi collector. - Baris 14-24 (rantai if): pemeriksaan berurutan berdasarkan awalan khas. Awalan
G1hanya dipakai collector G1;PSadalah tanda Parallel Scavenge;ZGCmilik ZGC; sementara kombinasiEden SpacedanTenured Gentanpa awalan adalah ciri Serial. - Urutan pemeriksaan penting:
ZGC Old Generationmengandung kataOld, tetapi tidak tertukar dengan G1 karena pemeriksaanstartsWith("G1")dilakukan lebih dulu terhadap seluruh nama pool.
Karakter G1 dan ZGC #
G1 (Garbage First) membagi heap menjadi ribuan region berukuran sama (1-32 MB, tergantung ukuran heap) alih-alih tiga generasi runtut. Region mana pun dapat berperan sebagai Eden, Survivor, atau Old, dan region Old yang paling banyak berisi sampah dikumpulkan lebih dulu — dari sini nama garbage first berasal. Penandaan hidup berjalan concurrent bersama aplikasi, disusul evakuasi singkat yang stop-the-world; hasilnya jeda khas di bawah puluhan milidetik dan target jeda yang bisa diatur lewat -XX:MaxGCPauseMillis.
ZGC (Z Garbage Collector) dirancang agar jeda tidak lagi skala dengan ukuran heap — pemindahan objek terjadi sambil aplikasi berjalan, dimungkinkan oleh colored pointers dan load barriers di level VM. Sejak JEP 474, ZGC berjalan mode generational: objek muda dan tua dipisahkan agar objek berumur pendek — mayoritas sampah di aplikasi khas — dikumpulkan dengan upaya jauh lebih kecil. Jeda khasnya di bawah satu milidetik bahkan pada heap puluhan GB. Bayarannya ada pada jejak memori lebih besar untuk metadata dan sedikit CPU untuk load barrier.
Memilih Berdasar Beban Kerja #
Kerangka memilihnya sederhana. Ukur dulu SLA aplikasi: berapa milidetik jeda yang masih bisa diterima klien, dan berapa persen waktu CPU yang boleh habis untuk GC. Bila jeda panjang tidak berarti — laporan malam, ETL, komputasi ilmiah — Parallel memberi throughput terbaik. Bila jeda puluhan milidetik cukup dan heap berukuran sedang (hingga belasan GB), G1 seimbang di kedua sisi. Bila setiap milidetik dihitung atau heap puluhan GB, ZGC layak diuji dengan membayar memori ekstra. Serial tetap berharga di container satu CPU tempat overhead thread GC justru memperburuk keadaan. Setelah collector dipilih, barulah ukuran heap dan generasi disetel — alur kerja lengkapnya kita rangkum pada Chapter Memory Tuning di akhir Section ini.
Referensi #
- Oracle: Garbage Collection Tuning Guide (JDK 25)
- JEP 474: ZGC: Generational Mode by Default
- dev.java: ZGC Overview
- Oracle: java Tool Reference (JDK 25) — opsi GC
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
DeteksiKolektor.java
Unduh berkasimport java.lang.management.ManagementFactory;
import java.util.List;
public class DeteksiKolektor {
public static void main(String[] args) {
List<String> pool = ManagementFactory.getMemoryPoolMXBeans().stream()
.map(b -> b.getName())
.filter(n -> n.contains("Eden") || n.contains("Old")
|| n.contains("Tenured") || n.contains("Young"))
.sorted()
.toList();
String kolektor;
if (pool.stream().anyMatch(n -> n.startsWith("G1"))) {
kolektor = "G1";
} else if (pool.stream().anyMatch(n -> n.startsWith("PS"))) {
kolektor = "Parallel";
} else if (pool.stream().anyMatch(n -> n.startsWith("ZGC"))) {
kolektor = "ZGC";
} else if (pool.contains("Tenured Gen")) {
kolektor = "Serial";
} else {
kolektor = "tidak dikenali";
}
System.out.println("kolektor aktif : " + kolektor);
pool.forEach(n -> System.out.println(" pool: " + n));
}
}