Dua chapter sebelumnya kita meminjam WeakReference sebagai jendela observasi tanpa membahas kelasnya. Saatnya membuka paket java.lang.ref secara utuh: Java menyediakan empat tingkat kekuatan referensi, dan tingkat kekuatan menentukan sikap GC terhadap objek yang ditunjuk. Referensi strong menjaga objek mati-matian; soft bersedia melepas saat memori menipis; weak langsung menyerah pada setiap koleksi; phantom bekerja setelah objek berakhir, sebagai penanda untuk pembersihan. Empat tingkat ini adalah alat presisi untuk masalah nyata: cache yang patuh tekanan memori, pemetaan kanonik yang tidak menahan kuncinya, dan pembersihan resource di luar heap. Pada chapter ini kita bedah keempatnya lalu membuktikan perilakunya dalam satu program.

Empat Tingkat Kekuatan #

JenisKelasGC memungut kapanPemakaian lazim
Strongpenulisan biasa (Objek x = ...)selama masih terjangkau — tak dipungutdefault seluruh program
SoftSoftReference<T>sebelum OutOfMemoryError, saat heap menipiscache sensitif memori
WeakWeakReference<T>pada koleksi berikutnya begitu tak ada strong/softpemetaan kanonik, metadata
PhantomPhantomReference<T>setelah objek berakhir; masuk ReferenceQueuepembersihan pasca-objek

Dua aturan membaca tabel ini. Pertama, urutannya hirarkis: objek tetap hidup selama masih dijangkau referensi yang lebih kuat — keberadaan WeakReference tidak menolong objek bila strong sudah lepas, dan sebaliknya SoftReference masih menahan objek di saat WeakReference sudah dikembalikan null. Kedua, get() pada PhantomReference selalu mengembalikan null sejak diciptakan — phantom memang bukan jendela untuk membaca objek, melainkan penanda bahwa objek sudah tidak lagi bisa dibangkitkan.

Empat Referensi dalam Satu Program #

Program berikut menyiapkan tiga objek berbeda — satu per jenis referensi non-strong — lalu mengamati dua momen: saat objek masih dipegang variabel strong, dan setelah variabel tersebut dilepas disertai System.gc():

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

public class EmpatReferensi {
    public static void main(String[] args) {
        StringBuilder dataLemah = new StringBuilder("pesan-lemah");
        StringBuilder dataLunak = new StringBuilder("pesan-lunak");
        StringBuilder dataHantu = new StringBuilder("pesan-hantu");

        WeakReference<StringBuilder> lemah = new WeakReference<>(dataLemah);
        SoftReference<StringBuilder> lunak = new SoftReference<>(dataLunak);
        ReferenceQueue<StringBuilder> antrean = new ReferenceQueue<>();
        PhantomReference<StringBuilder> hantu = new PhantomReference<>(dataHantu, antrean);

        System.out.println("saat masih reachable:");
        System.out.println("  lemah.get() = " + lemah.get());
        System.out.println("  lunak.get() = " + lunak.get());
        System.out.println("  hantu.get() = " + hantu.get());

        dataLemah = null;
        dataLunak = null;
        dataHantu = null;
        System.gc();

        System.out.println("setelah strong reference dilepas + System.gc():");
        System.out.println("  lemah.get() = " + lemah.get());
        System.out.println("  lunak.get() = " + lunak.get());
        System.out.println("  hantu masuk antrean? " + (antrean.poll() != null));
    }
}

Output yang Dihasilkan:

text
saat masih reachable:
  lemah.get() = pesan-lemah
  lunak.get() = pesan-lunak
  hantu.get() = null
setelah strong reference dilepas + System.gc():
  lemah.get() = null
  lunak.get() = pesan-lunak
  hantu masuk antrean? true

Mari kita bedah hasilnya baris demi hasil:

  1. Tiga baris saat masih reachable: lemah.get() dan lunak.get() mengembalikan objek — referensi lemah maupun lunak tetap bisa membaca objek selama strong reference masih ada. Sebaliknya hantu.get() sudah null sejak awal, meski objek jelas masih hidup — perilaku baku phantom.
  2. Baris lemah.get() = null: begitu satu-satunya strong reference dilepas, koleksi berikutnya memungut objek dan mengosongkan WeakReference. Pola inilah yang dipakai dua chapter sebelumnya sebagai jendela observasi.
  3. Baris lunak.get() = pesan-lunak: SoftReference menolak melepas objek karena heap masih lapang. Soft baru menyerah ketika JVM butuh ruang menjelang OutOfMemoryError — pada program ini memori berlimpah, maka objek bertahan.
  4. Baris hantu masuk antrean? true: objek yang dipegang phantom sudah dipungut, dan phantom-nya masuk ReferenceQueue — sinyal pembersihan yang bisa dipol oleh thread pembersih.

Soft untuk Cache, Weak untuk Pemetaan #

Semantik soft — “tahan selama memori memungkinkan” — adalah bahan cache yang tepat. Cache gambar thumbnail misalnya: kecocokan cache (cache hit) mempercepat respons, tetapi isinya boleh dibuang kapan pun demi kelangsungan aplikasi. Implementasinya membungkus nilai cache dengan SoftReference; saat heap menipis, JVM memungut nilai cache lebih dulu sebelum membiarkan aplikasi kehabisan memori, dan cache cukup memuat ulang saat dibutuhkan. Pola ini menggantikan “cache tanpa eviksi” yang kita vonis sebagai sumber kebocoran pada Chapter Memory Leaks — kini eviksinya diserahkan ke GC.

Weak menjawab kebutuhan sebaliknya: metadata yang tidak boleh memperpanjang umur objeknya. Kandidat terbesarnya adalah pemetaan kanonik — WeakHashMap menyimpan kunci dengan WeakReference, sehingga entri otomatis terpangkas begitu kunci tidak lagi dirujuk program:

java Pola WeakHashMap (potongan)
WeakHashMap<Object, String> peta = new WeakHashMap<>();
Object kunci = new Object();
peta.put(kunci, "nilai");
kunci = null;      // kunci kini hanya dipegang lemah oleh peta
System.gc();       // pada eksekusi berikut, entri dipangkas saat peta dipakai

Pada pengujian di mesin uji, peta.size() kadang sudah 0 tepat setelah pemanggilan berikutnya dan kadang masih 1 sebelum pemangkasan berjalan — pemangkasan WeakHashMap terjadi saat peta dipakai, bukan pada detik GC berlangsung. Kerangka ThreadLocal memakai pola serupa pada kunci internalnya, namun nilai tetap tertahan seumur thread — itulah mengapa remove() tetap wajib sebagaimana dibahas Chapter Memory Leaks.

Phantom untuk Pembersihan Pasca-Objek #

Phantom reference bekerja bersama ReferenceQueue sebagai notifikasi pascapemungutan. Thread pembersih memanggil queue.remove() yang memblokir hingga ada phantom masuk, lalu menjalankan pembersihan — misalnya membebaskan memori native yang dialokasikan lewat FFM API untuk objek tersebut. Berbeda dengan finalize() yang usang dan tidak dapat dipercaya, phantom tidak mencoba “membangkitkan” objek: referensinya memang tak bisa membaca objek, sehingga tidak ada celah logika untuk menghidupkan kembali. Alat modern seperti Cleaner (java.lang.ref) dibangun di atas pola ini; untuk resource biasa, try-with-resources tetap pilihan utama, dan phantom hanya baris pertahanan terakhir.

Dengan empat kekuatan referensi di tangan, perangkat konseptual kita lengkap — yang belum lengkap adalah kendali operasionalnya: bagaimana mengatur ukuran heap, memilih collector, dan mengaktifkan diagnostik lewat command line. Bab-bab berikutnya pindah dari API ke command line, dimulai dari kamus para benderanya: Chapter JVM Options.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

EmpatReferensi.java

Unduh berkas
java
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

public class EmpatReferensi {
    public static void main(String[] args) {
        StringBuilder dataLemah = new StringBuilder("pesan-lemah");
        StringBuilder dataLunak = new StringBuilder("pesan-lunak");
        StringBuilder dataHantu = new StringBuilder("pesan-hantu");

        WeakReference<StringBuilder> lemah = new WeakReference<>(dataLemah);
        SoftReference<StringBuilder> lunak = new SoftReference<>(dataLunak);
        ReferenceQueue<StringBuilder> antrean = new ReferenceQueue<>();
        PhantomReference<StringBuilder> hantu = new PhantomReference<>(dataHantu, antrean);

        System.out.println("saat masih reachable:");
        System.out.println("  lemah.get() = " + lemah.get());
        System.out.println("  lunak.get() = " + lunak.get());
        System.out.println("  hantu.get() = " + hantu.get());

        dataLemah = null;
        dataLunak = null;
        dataHantu = null;
        System.gc();

        System.out.println("setelah strong reference dilepas + System.gc():");
        System.out.println("  lemah.get() = " + lemah.get());
        System.out.println("  lunak.get() = " + lunak.get());
        System.out.println("  hantu masuk antrean? " + (antrean.poll() != null));
    }
}