CodeGym /Java Blog /Acak /Referensi Phantom di Jawa
John Squirrels
Level 41
San Francisco

Referensi Phantom di Jawa

Dipublikasikan di grup Acak
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:
  1. StrongReference (referensi biasa yang kita buat saat membuat objek):

    Cat cat = new Cat()

    Dalam contoh ini, kucing adalah referensi yang kuat.

  2. SoftReference (referensi lembut). Kami mendapat pelajaran tentang referensi semacam itu.

  3. WeakReference (referensi lemah). Ada juga pelajaran tentang mereka di sini .

  4. PhantomReference (referensi hantu).

Tiga yang terakhir adalah tipe umum dengan parameter tipe (misalnya, SoftReference<Integer> , WeakReference<MyClass> ). Kelas SoftReference , WeakReference , dan PhantomReference diwariskan dari kelas Referensi . Metode yang paling penting saat bekerja dengan kelas-kelas ini adalah:
  • get() — mengembalikan objek yang direferensikan;

  • clear() — menghapus referensi ke objek.

Anda ingat metode ini dari pelajaran tentang SoftReference dan WeakReference . Penting untuk diingat bahwa mereka bekerja secara berbeda dengan jenis referensi yang berbeda. Hari ini kita tidak akan menyelami tiga tipe pertama. Sebagai gantinya, kita akan berbicara tentang referensi hantu. Kami akan menyentuh jenis referensi lain, tetapi hanya sehubungan dengan perbedaannya dari referensi hantu. Ayo pergi! :) Pertama-tama, mengapa kita membutuhkan referensi hantu? Seperti yang Anda ketahui, pengumpul sampah (gc) melepaskan memori yang digunakan oleh objek Java yang tidak perlu. Kolektor menghapus objek dalam dua "lintasan". Pada pass pertama, itu hanya melihat objek, dan, jika perlu, menandainya sebagai "tidak perlu" (artinya, "untuk dihapus"). Jikafinalize()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 .

Hal yang paling penting untuk dipahami saat bekerja dengan referensi phantom adalah bahwa objek tidak dihapus dari memori hingga referensi phantomnya berada dalam antrian ini. Itu akan dihapus hanya setelah metode clear() dipanggil pada referensi phantom. Mari kita lihat sebuah contoh. Pertama, kita akan membuat kelas pengujian yang akan menyimpan beberapa jenis data.

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! :)
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION