Ketika kita menulis new Pesanan() sepuluh ribu kali, heap berisi sepuluh ribu objek — tetapi informasi tentang class Pesanan itu sendiri hanya ada satu. Struktur field, bytecode method, nama class, dan constant pool bukanlah data objek; semuanya adalah metadata class yang harus tinggal di suatu tempat. Jawabannya adalah Metaspace, area memori terpisah yang sejak Java 8 menggantikan ruang lawas bernama PermGen. Perpindahan ini bukan kosmetik — lokasi dan cara Metaspace tumbuh mengubah cara kita mendiagnosis galat memori yang melibatkan class loading. Pada chapter ini kita bedah isi Metaspace, alasan PermGen dipensiunkan, dan cara membatasi agar class loader yang bocor tidak menggerus memori sistem.
Metadata Class Bukan Objek #
Bayangkan class Pesanan dengan dua field dan tiga method. Di heap, setiap instance hanya menyimpan nilai field milik objek itu — total dan status milik siapa. Sementara itu, pertanyaan “field apa saja yang dimiliki class ini”, “bytecode method hitungTotal apa”, dan “class apa induknya” dijawab oleh struktur tunggal yang dibuat class loader saat fase loading. Struktur inilah yang menempati Metaspace: representasi internal java.lang.Class (berbeda dari objek Class yang kita lihat dari Java), tabel method, informasi field, dan constant pool hasil resolution.
Pemisahan ini menjelaskan gejala yang sering membingungkan pemula. Menambah jutaan objek membebani heap dan diurus garbage collector; memuat class tanpa henti membebani Metaspace yang bukan wilayah garbage collector utama. Kedua ruang itu diukur, dibatasi, dan galatnya berbeda — heap kehabisan ruang melempar OutOfMemoryError: Java heap space, Metaspace kehabisan ruang melempar OutOfMemoryError: Metaspace.
Dari PermGen ke Metaspace #
Sebelum Java 8, metadata class tinggal di wilayah bernama Permanent Generation (PermGen) yang letaknya di dalam heap JVM. PermGen punya ukuran tetap sejak startup — diatur -XX:MaxPermSize — dan dua kelemahan serius. Pertama, menebak ukuran tetap untuk sesuatu yang tumbuh dinamis sulit: terlalu kecil berujung OutOfMemoryError: PermGen space saat aplikasi memuat banyak class; terlalu besar berarti memori terbuang. Kedua, garbage collector harus ikut mengurus wilayah tersebut padahal class jarang berubah — bekerja sia-sia. JEP 122 memensiunkan PermGen dan memindahkan metadata ke memori native — memori sistem operasi di luar heap — dengan nama Metaspace.
| Aspek | PermGen (Java ≤ 7) | Metaspace (Java ≥ 8) |
|---|---|---|
| Lokasi | di dalam heap JVM | memori native sistem operasi |
| Batas bawaan | tetap, wajib diatur | tumbuh sesuai kebutuhan |
| Pengatur | -XX:MaxPermSize | -XX:MaxMetaspaceSize |
| Isi | metadata class + sebagainya | metadata class saja |
Karena bertempat di memori native, ukuran Metaspace tidak dihitung dalam -Xmx. Server dengan -Xmx4g bisa saja kehabisan RAM karena Metaspace tumbuh tak terkendali — inilah alasan batas tetap perlu dipasang di lingkungan produksi, sebagaimana kita praktikkan di bagian akhir chapter.
Metaspace di Balik Layar #
Metaspace tumbuh per class yang dimuat dan menyusut saat class di-unload — class menjadi tidak terpakai ketika class loader pembuatnya sendiri sudah tidak terjangkau. Wilayahnya dibagi dua bagian utama: area metadata inti dan compressed class space, tempat struktur yang memanfaatkan compressed oops agar referensi objek cukup disandikan 32 bit. Kedua bagian ini terlihat sebagai memory pool tersendiri lewat API java.lang.management. Program berikut memetakan seluruh pool memori yang dikenali JVM, lengkap dengan jenisnya:
import java.lang.management.ManagementFactory;
public class PoolMemori {
public static void main(String[] args) {
ManagementFactory.getMemoryPoolMXBeans().stream()
.sorted((a, b) -> a.getName().compareTo(b.getName()))
.forEach(b -> System.out.printf("%-28s %s%n", b.getName(), b.getType()));
}
}Output yang Dihasilkan:
CodeHeap 'non-nmethods' Non-heap memory
CodeHeap 'non-profiled nmethods' Non-heap memory
CodeHeap 'profiled nmethods' Non-heap memory
Compressed Class Space Non-heap memory
G1 Eden Space Heap memory
G1 Old Gen Heap memory
G1 Survivor Space Heap memory
Metaspace Non-heap memoryMari kita baca peta tersebut:
- Baris
Metaspace Non-heap memory: pool Metaspace berdiri terpisah dari tiga poolG1 ...yang bertipe heap. JVM dengan collector lain menampilkan nama generasi berbeda —Eden SpacedanTenured Genpada Serial GC — namun barisMetaspaceselalu hadir. Compressed Class Space: pasangan Metaspace untuk struktur class yang memakai penyandian referensi terkompresi; terpisah agar bagian inti Metaspace tetap bisa tumbuh leluasa.- Tiga pool
CodeHeap ...: wilayah untuk kode mesin hasil kompilasi JIT — bukan metadata class, tetapi sama-sama non-heap dan ditampilkan bersama karena mengikuti antarmukaMemoryPoolMXBeanyang sama.
Nilai getType() memastikan pembacaan kita: pool G1 bertipe heap memory, sisanya non-heap. Daftar pool di atas stabil untuk JVM yang sama, sehingga pool names menjadi pegangan yang aman untuk diobservasi — berbeda dengan angka pemakaian byte yang berubah setiap detik.
Membatasi dengan MaxMetaspaceSize #
Secara bawaan Metaspace tumbuh tanpa batas atas — MaxMetaspaceSize diisi nilai maksimum bilangan tak bertanda sebagai penanda “tak terbatas”, terlihat dari bendera final JVM:
java -XX:+PrintFlagsFinal -version | grep Metaspace size_t MaxMetaspaceSize = 18446744073709551615 {product} {default}
size_t MetaspaceSize = 22020096 {product} {default}Angka 18446744073709551615 adalah nilai maksimum size_t — sinyal bahwa tidak ada batas. Batas bisa dipasang, dan batas itu bisa terlampaui ketika program membuat class terus-menerus. Demo berikut memuat ulang bytecode class kecil KelasKecil lewat class loader baru di tiap iterasi — tiap loader menciptakan class baru di Metaspace — lalu menyimpan class tersebut agar tidak dapat di-unload:
byte[] bytes = Files.readAllBytes(Path.of("KelasKecil.class"));
List<Class<?>> tetapHidup = new ArrayList<>();
for (int i = 1; i <= 100_000; i++) {
ClassLoader loader = new ClassLoader(null) { // induk: bootstrap saja
@Override
protected Class<?> findClass(String name) {
return defineClass(name, bytes, 0, bytes.length);
}
};
tetapHidup.add(Class.forName("KelasKecil", true, loader));
}Dengan heap dan Metaspace tak dibatasi, seratus ribu class itu menghabiskan sekitar 129 MB Metaspace pada mesin uji — angka pastinya bergantung ukuran class. Jalankan dengan batas, dan hasilnya berubah total:
java -XX:MaxMetaspaceSize=8m MesinKelasException in thread "main" java.lang.OutOfMemoryError: Metaspace
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:962)Pesan OutOfMemoryError: Metaspace menegaskan bahwa yang habis bukan heap — defineClass gagal karena metadata tidak kebagian tempat. Pola seperti demo ini bukan sekadar teori: application server lawas, framework yang membangkitkan proxy dinamis, dan engine scripting di atas JVM dapat mengalami hal serupa ketika class loader lama tidak pernah dilepas.
Metaspace menuntaskan peta area memori JVM — objek di heap, frame di stack, metadata class di Metaspace. Kini kita siap membahas penghuni paling terkenal dari wilayah tersebut: bagaimana garbage collector menentukan objek mana yang masih hidup, dan bagaimana JVM membersihkan sisanya. Itulah pokok Chapter Garbage Collection berikutnya.
Referensi #
- JEP 122: Remove the Permanent Generation
- Oracle: API java.lang.management (Java SE 25)
- Oracle: java Tool Reference (JDK 25)
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
PoolMemori.java
Unduh berkasimport java.lang.management.ManagementFactory;
public class PoolMemori {
public static void main(String[] args) {
ManagementFactory.getMemoryPoolMXBeans().stream()
.sorted((a, b) -> a.getName().compareTo(b.getName()))
.forEach(b -> System.out.printf("%-28s %s%n", b.getName(), b.getType()));
}
}