Hai! Dalam pembahasan hari ini, kita akan membahas secara rinci tentang "referensi hantu" (PhantomReference) di Jawa. Referensi macam apa ini? Mengapa mereka disebut "referensi hantu"? Bagaimana mereka digunakan? Seperti yang Anda ingat, Java memiliki 4 jenis referensi:
-
StrongReference (referensi biasa yang kita buat saat membuat objek):
Cat cat = new Cat()
Dalam contoh ini, kucing adalah referensi yang kuat.
-
SoftReference (referensi lembut). Kami mendapat pelajaran tentang referensi semacam itu.
-
WeakReference (referensi lemah). Ada juga pelajaran tentang mereka di sini .
-
PhantomReference (referensi hantu).
-
get() — mengembalikan objek yang direferensikan;
- clear() — menghapus referensi ke objek.
finalize()
metode telah diganti untuk objek, itu disebut. Atau mungkin tidak disebut - semuanya tergantung apakah Anda beruntung. Anda mungkin ingat itu finalize()
berubah-ubah :) Pada pass kedua pengumpul sampah, objek dihapus dan memori dibebaskan. Perilaku tak terduga pemulung menimbulkan sejumlah masalah bagi kami. Kami tidak tahu persis kapan pengumpul sampah akan mulai berjalan. Kami tidak tahu apakah finalize()
metode ini akan dipanggil. Plus, referensi yang kuat ke suatu objek dapat dibuat saat metodenya finalize()
dieksekusi, dalam hal ini objek tidak akan dihapus sama sekali. Untuk program yang menuntut banyak pada memori yang tersedia, ini dapat dengan mudah menyebabkan file OutOfMemoryError
. Semua ini mendorong kita untuk menggunakan referensi hantu. Faktanya adalah ini mengubah perilaku pemulung. Jika objek hanya memiliki referensi hantu, maka:
-
metode finalize() dipanggil (jika diganti)
-
jika tidak ada yang berubah setelah metode finalize() selesai dan objek masih dapat dihapus, maka referensi phantom ke objek ditempatkan dalam antrian khusus: ReferenceQueue .
public class TestClass {
private StringBuffer data;
public TestClass() {
this.data = new StringBuffer();
for (long i = 0; i < 50000000; i++) {
this.data.append('x');
}
}
@Override
protected void finalize() {
System.out.println("The finalize method has been called on the TestClass object");
}
}
Saat kita membuat objek, kita sengaja memberi mereka "beban" yang besar dan kuat (dengan menambahkan 50 juta karakter "x" ke setiap objek) untuk menggunakan lebih banyak memori. Selain itu, kami mengganti metode finalize() untuk memastikannya dijalankan. Selanjutnya, kita membutuhkan kelas yang akan diwarisi dari PhantomReference . Mengapa kita membutuhkan kelas seperti itu? Semuanya mudah. Ini akan memungkinkan kita untuk menambahkan logika tambahan ke metode clear() untuk memverifikasi bahwa referensi phantom benar-benar dihapus (yang berarti objek telah dihapus).
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class MyPhantomReference<TestClass> extends PhantomReference<TestClass> {
public MyPhantomReference(TestClass obj, ReferenceQueue<TestClass> queue) {
super(obj, queue);
Thread thread = new QueueReadingThread<TestClass>(queue);
thread.start();
}
public void cleanup() {
System.out.println("Cleaning up a phantom reference! Removing an object from memory!");
clear();
}
}
Selanjutnya, kita membutuhkan utas terpisah yang akan menunggu pengumpul sampah melakukan tugasnya, dan tautan hantu akan muncul di ReferenceQueue kita . Segera setelah referensi tersebut berakhir di antrean, metode cleanup() akan dipanggil:
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
public class QueueReadingThread<TestClass> extends Thread {
private ReferenceQueue<TestClass> referenceQueue;
public QueueReadingThread(ReferenceQueue<TestClass> referenceQueue) {
this.referenceQueue = referenceQueue;
}
@Override
public void run() {
System.out.println("The thread monitoring the queue has started!");
Reference ref = null;
// Wait until the references appear in the queue
while ((ref = referenceQueue.poll()) == null) {
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
throw new RuntimeException("Thread " + getName() + " was interrupted!");
}
}
// As soon as a phantom reference appears in the queue, clean it up
((MyPhantomReference) ref).cleanup();
}
}
Dan terakhir, kita membutuhkan metode main() , yang akan kita tempatkan di kelas Utama yang terpisah . Dalam metode itu, kita akan membuat objek TestClass , referensi phantom untuknya, dan antrean untuk referensi phantom. Setelah itu, kami akan memanggil pengumpul sampah dan melihat apa yang terjadi :)
import java.lang.ref.*;
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(10000);
ReferenceQueue<TestClass> queue = new ReferenceQueue<>();
Reference ref = new MyPhantomReference<>(new TestClass(), queue);
System.out.println("ref = " + ref);
Thread.sleep(5000);
System.out.println("Collecting garbage!");
System.gc();
Thread.sleep(300);
System.out.println("ref = " + ref);
Thread.sleep(5000);
System.out.println("Collecting garbage!");
System.gc();
}
}
Keluaran konsol:
ref = MyPhantomReference@4554617c
The thread monitoring the queue has started!
Collecting garbage!
The finalize method has been called on the TestClass object
ref = MyPhantomReference@4554617c
Collecting garbage!
Cleaning up a phantom reference!
Removing an object from memory!
Apa yang kita lihat di sini? Semuanya terjadi seperti yang kita rencanakan! Metode finalize() objek kita diganti dan dipanggil saat pengumpul sampah sedang berjalan. Selanjutnya, referensi hantu dimasukkan ke dalam ReferenceQueue . Sementara di sana, metode clear() dipanggil (di mana kami memanggil cleanup() untuk menampilkan ke konsol). Akhirnya, objek tersebut dihapus dari memori. Sekarang Anda melihat dengan tepat bagaimana ini bekerja :) Tentu saja, Anda tidak perlu menghafal semua teori tentang referensi hantu. Tapi alangkah baiknya jika Anda mengingat setidaknya poin-poin utamanya. Pertama, ini adalah referensi terlemah dari semuanya. Mereka ikut bermain hanya jika tidak ada referensi lain ke objek yang tersisa. Daftar referensi yang kami berikan di atas diurutkan dalam urutan dari yang terkuat ke terlemah: StrongReference -> SoftReference -> WeakReference -> PhantomReference Referensi phantom memasuki pertempuran hanya jika tidak ada referensi kuat, lunak, atau lemah ke objek kita : ) Kedua, metode get() selalu mengembalikan null untuk referensi hantu. Berikut adalah contoh sederhana di mana kami membuat tiga jenis referensi untuk tiga jenis mobil yang berbeda:
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
public class Main {
public static void main(String[] args) {
Sedan sedan = new Sedan();
HybridAuto hybrid = new HybridAuto();
F1Car f1car = new F1Car();
SoftReference<Sedan> softReference = new SoftReference<>(sedan);
System.out.println(softReference.get());
WeakReference<HybridAuto> weakReference = new WeakReference<>(hybrid);
System.out.println(weakReference.get());
ReferenceQueue<F1Car> referenceQueue = new ReferenceQueue<>();
PhantomReference<F1Car> phantomReference = new PhantomReference<>(f1car, referenceQueue);
System.out.println(phantomReference.get());
}
}
Keluaran konsol:
Sedan@4554617c
HybridAuto@74a14482
null
Metode get() mengembalikan objek yang sepenuhnya biasa untuk referensi lunak dan lemah, tetapi mengembalikan nol untuk referensi hantu. Ketiga, referensi hantu terutama digunakan dalam prosedur yang rumit untuk menghapus objek dari memori. Itu dia! :) Itu mengakhiri pelajaran kita hari ini. Tetapi Anda tidak dapat mempelajari teori sendirian, jadi inilah saatnya untuk kembali menyelesaikan tugas! :)
GO TO FULL VERSION