JavaRush /Blog Java /Random-MS /PhantomReference dalam Java

PhantomReference dalam Java

Diterbitkan dalam kumpulan
hello! Dalam pelajaran hari ini, kita akan bercakap secara terperinci tentang Rujukan Phantom di Jawa. Apakah jenis pautan ini, mengapa ia dipanggil "hantu" dan cara menggunakannya? Seperti yang anda ingat, terdapat 4 jenis pautan di Jawa:
  1. StrongReference (rujukan biasa yang kami buat semasa mencipta objek):

    Cat cat = new Cat()

    kucing dalam contoh ini adalah rujukan yang Kuat.

  2. SoftReference (rujukan lembut). Kami mengadakan kuliah tentang pautan ini.

  3. WeakReference (rujukan lemah). Terdapat kuliah tentang mereka juga, di sini .

  4. PhantomReference (rujukan hantu).

Objek daripada tiga jenis terakhir ditaip (contohnya, SoftReference<Integer> , WeakReference<MyClass> ). Kelas SoftReference, WeakReferencedan PhantomReferencewarisi daripada kelas Reference. Kaedah yang paling penting apabila bekerja dengan kelas ini ialah:
  • get()- mengembalikan objek yang dirujuk oleh pautan ini;

  • clear()— memadam rujukan kepada objek.

Anda masih ingat kaedah ini daripada kuliah tentang SoftReferencedan WeakReference. Adalah penting untuk diingat bahawa ia berfungsi secara berbeza dengan jenis pautan yang berbeza. Hari ini kita tidak akan mempertimbangkan tiga jenis pertama secara terperinci, tetapi akan bercakap tentang pautan hantu. Kami juga akan menyentuh jenis pautan lain, tetapi hanya pada bahagian di mana kami akan bercakap tentang cara pautan hantu berbeza daripadanya. Pergi! :) Mari kita mulakan dengan sebab kita memerlukan pautan hantu di tempat pertama. Seperti yang anda ketahui, pengumpul sampah (Pengumpul Sampah atau gc) bertanggungjawab untuk membebaskan memori daripada objek Java yang tidak diperlukan . Pengumpul mengeluarkan objek dalam dua "laluan". Pada laluan pertama, ia hanya melihat objek, dan, jika perlu, menandakannya sebagai "tidak perlu dan akan dipadamkan." Jika objek ini mempunyai kaedah yang ditindih finalize(), ia dipanggil. Atau ia tidak dipanggil - bergantung pada nasib anda. Anda mungkin ingat bahawa ini finalize()adalah perkara yang berubah-ubah :) Dalam pas kedua pengumpul, objek dipadamkan dan memori dibebaskan. Tingkah laku pengutip sampah yang tidak menentu ini menimbulkan beberapa masalah kepada kita. Kami tidak tahu bila pemungut sampah akan mula bekerja. Kami tidak tahu sama ada kaedah itu akan dipanggil finalize(). Selain itu, semasa operasi finalize()pautan yang kuat ke objek boleh dibuat, dan kemudian ia tidak akan dipadamkan sama sekali. Pada sistem yang haus ingatan, ini boleh membawa kepada OutOfMemoryError. Semua ini mendorong kami ke arah menggunakan pautan hantu . Intinya ialah ini mengubah tingkah laku pemungut sampah. Jika terdapat hanya pautan hantu yang tersisa ke objek, maka ia mempunyai:
  • kaedah dipanggil finalize()(jika ia ditindih);

  • jika selepas kerja finalize()tiada apa yang berubah dan objek masih boleh dipadamkan, rujukan hantu kepada objek diletakkan dalam baris gilir khas - ReferenceQueue.

Perkara yang paling penting untuk difahami apabila bekerja dengan rujukan hantu ialah objek tidak dialih keluar daripada ingatan selagi rujukan hantunya berada dalam baris gilir ini . Ia akan dipadamkan hanya selepas pautan hantu clear(). Mari kita lihat contoh. Mula-mula, mari buat kelas ujian yang akan menyimpan beberapa 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("У an object TestClass вызван метод finalize!!!");
   }
}
Kami sengaja "memuatkan" objek dengan data semasa menciptanya (menambah 50 juta aksara "x" pada setiap objek) untuk mengambil lebih banyak memori. Di samping itu, kami secara khusus mengatasi kaedah finalize()untuk melihat bahawa ia berfungsi. Seterusnya kita memerlukan kelas yang akan mewarisi daripada PhantomReference. Mengapa kita memerlukan kelas sedemikian? Mudah sahaja. Dengan cara ini kita boleh menambah logik tambahan kepada kaedah clear()untuk melihat bahawa rujukan hantu sebenarnya telah dibersihkan (dan oleh itu objek telah dipadamkan).
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("Очистка фантомной ссылки! Удаление an object из памяти!");
       clear();
   }
}
Seterusnya, kami memerlukan benang berasingan yang akan menunggu pemungut sampah melakukan tugasnya, dan ReferenceQueuepautan hantu akan muncul dalam baris gilir kami. Sebaik sahaja pautan sedemikian masuk ke dalam baris gilir, kaedahnya akan dipanggil cleanup():
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("Поток, отслеживающий очередь, стартовал!");
       Reference ref = null;

       //ждем, пока в очереди появятся ссылки
       while ((ref = referenceQueue.poll()) == null) {

           try {
               Thread.sleep(50);
           }

           catch (InterruptedException e) {
               throw new RuntimeException("Поток " + getName() + " был прерван!");
           }
       }

       //How только в очереди появилась фантомная link - очистить ее
       ((MyPhantomReference) ref).cleanup();
   }
}
Dan akhirnya, kita memerlukan kaedah main(): mari letakkannya dalam kelas yang berasingan Main. Di dalamnya kita akan mencipta objek TestClass, rujukan hantu kepadanya dan baris gilir untuk rujukan hantu. Selepas itu kami akan memanggil pengutip sampah dan lihat apa yang berlaku :)
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("Вызывается сборка мусора!");

       System.gc();
       Thread.sleep(300);

       System.out.println("ref = " + ref);

       Thread.sleep(5000);

       System.out.println("Вызывается сборка мусора!");

       System.gc();
   }
}
Output konsol: ref = MyPhantomReference@4554617c Benang memantau baris gilir telah bermula! Kutipan sampah dipanggil! Kaedah akhir telah dipanggil pada objek TestClass!!! ref = MyPhantomReference@4554617c Pengumpulan sampah sedang dipanggil! Membersihkan pautan hantu! Mengeluarkan objek dari ingatan! Apa yang kita lihat di sini? Semuanya berlaku seperti yang kita rancang! Kelas objek kami mempunyai kaedah yang ditindih finalize()dan ia dipanggil semasa pengumpul sedang berjalan. Seterusnya, pautan hantu telah beratur ReferenceQueue. Di sana dia mempunyai kaedah yang dipanggil clear()(yang kami lakukan cleanup()untuk menambah output ke konsol). Akibatnya, objek telah dialih keluar daripada ingatan. Kini anda melihat dengan tepat cara ia berfungsi :) Sudah tentu, anda tidak perlu menghafal semua teori yang berkaitan dengan pautan hantu. Tetapi ia akan menjadi baik jika anda mengingati sekurang-kurangnya perkara utama. Pertama , ini adalah pautan yang paling lemah. Mereka mula bermain hanya apabila tiada rujukan lain kepada objek. Senarai pautan yang telah kami berikan di atas adalah dalam susunan kekuatan menurun: StrongReference-> SoftReference-> WeakReference-> PhantomReference Pautan hantu akan memasuki pertempuran hanya apabila tiada pautan Kuat, Lembut, atau Lemah ke objek kita :) Kedua , kaedah get()untuk pautan hantu sentiasa kembali null. Berikut ialah contoh mudah di mana kami mencipta tiga jenis pautan yang berbeza untuk tiga jenis kereta yang berbeza:
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());

   }
}
Output konsol: Sedan@4554617c HybridAuto@74a14482 null Kaedah get()mengembalikan objek yang agak normal untuk pautan lembut dan pautan lemah, tetapi dikembalikan nulluntuk yang hantu. Ketiga , kawasan utama penggunaan rujukan hantu adalah prosedur kompleks untuk mengeluarkan objek dari ingatan. Itu sahaja! :) Ini mengakhiri pelajaran kita untuk hari ini. Tetapi teori sahaja tidak akan membawa anda jauh, jadi sudah tiba masanya untuk kembali menyelesaikan masalah! :)
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION