CodeGym /Java Blog /Toto sisi /Java 中的虛引用
John Squirrels
等級 41
San Francisco

Java 中的虛引用

在 Toto sisi 群組發布
你好!在今天的討論中,我們將詳細討論Java中的“幻象引用”(PhantomReference)。這些是什麼參考資料?為什麼它們被稱為“幻影引用”?它們是如何使用的?你會記得,Java 有 4 種引用:
  1. StrongReference(我們在創建對象時創建的普通引用):

    Cat cat = new Cat()

    在此示例中,cat是一個強引用。

  2. SoftReference(軟引用)。我們有關於此類參考的教訓

  3. WeakReference(弱引用)。這裡也有關於他們的教訓。

  4. PhantomReference(虛引用)。

最後三個是具有類型參數的泛型類型(例如,SoftReference<Integer>WeakReference<MyClass>)。SoftReference 、WeakReferencePhantomReference繼承自Reference類。使用這些類時最重要的方法是:
  • get() — 返回引用的對象;

  • clear() — 刪除對對象的引用。

您還記得有關SoftReferenceWeakReference的課程中的這些方法。重要的是要記住,它們對不同類型的引用有不同的作用。今天我們不深入探討前三種類型。相反,我們將討論虛引用。我們將觸及其他類型的引用,但僅涉及它們與虛引用的區別。我們走吧!:) 首先,為什麼我們需要虛引用?如您所知,垃圾收集器 (gc) 釋放不需要的 Java 對象使用的內存。收集器分兩次“通過”刪除一個對象。在第一遍中,它只查看對象,並在必要時將它們標記為“不需要”(意思是“要刪除”)。如果finalize()對象的方法已被重寫,它被調用。或者也許它沒有被調用——這完全取決於你是否幸運。您可能還記得那finalize()是善變的 :) 在垃圾收集器的第二遍中,對像被刪除並釋放內存。垃圾收集器不可預知的行為給我們帶來了很多問題。我們不知道垃圾收集器何時開始運行。我們不知道該finalize()方法是否會被調用。finalize()另外,可以在執行對象的方法時創建對對象的強引用,在這種情況下根本不會刪除該對象。對於對可用內存有大量要求的程序,這很容易導致OutOfMemoryError. 所有這些都促使我們使用幻象引用. 事實上,這會改變垃圾收集器的行為。如果對像只有虛引用,則:
  • 它的finalize()方法被調用(如果它被覆蓋)

  • 如果在finalize()方法完成後沒有任何變化並且對象仍然可以被刪除,那麼對該對象的幻影引用被放置在一個特殊的隊列中:ReferenceQueue

使用幻影引用時要理解的最重要的一點是,只有當幻影引用進入隊列時,對象才會從內存中刪除。只有在對幻像引用調用clear()方法後,它才會被刪除。讓我們看一個例子。首先,我們將創建一個測試類來存儲某種數據。

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");
   }
}
當我們創建對象時,我們會故意給它們一個沉重的“負載”(通過向每個對象添加 5000 萬個“x”字符)以佔用更多內存。此外,我們覆蓋finalize()方法以查看它是否運行。接下來,我們需要一個繼承自PhantomReference的類。為什麼我們需要這樣一個類?一切都很簡單。這將允許我們向clear()方法添加額外的邏輯,以驗證幻影引用是否真的被清除(這意味著對像已被刪除)。

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();
   }
}
接下來,我們需要一個單獨的線程來等待垃圾收集器完成它的工作,幻象鏈接將出現在我們的ReferenceQueue中。一旦這樣的引用在隊列中結束,就會對其調用 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("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();
   }
}
最後,我們需要main()方法,我們將把它放在一個單獨的Main類中。在那個方法中,我們將創建一個TestClass對象、一個對它的虛引用,以及一個虛引用隊列。之後,我們將調用垃圾收集器,看看會發生什麼:)

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();
   }
}
控制台輸出:

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! 
我們在這裡看到了什麼?一切都按照我們的計劃發生了!我們對象的finalize()方法被重寫,並且在垃圾收集器運行時被調用。接下來,幻象引用被放入ReferenceQueue。在那裡,調用了它的clear()方法(我們在其中調用了cleanup()以便輸出到控制台)。最後,該對像從內存中刪除。現在你明白這是如何工作的了:) 當然,你不需要記住所有關於虛引用的理論。但是,如果您至少記住要點,那將是很好的。首先,這些是所有參考文獻中最弱的。只有當沒有其他對該對象的引用被留下時,它們才會起作用。 我們上面給出的引用列表按從強到弱的降序排列: StrongReference -> SoftReference -> WeakReference -> PhantomReference 只有當我們的對像沒有強引用、軟引用或弱引用時,幻引用才會進入戰鬥: ) 其次,get()方法總是為幻象引用返回null 。這是一個簡單的例子,我們為三種不同類型的汽車創建了三種不同類型的引用:

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());

   }
}
控制台輸出:

Sedan@4554617c
HybridAuto@74a14482 
null
get ()方法為軟引用和弱引用返回了完全普通的對象,但為幻引用返回了null 。第三,幻象引用主要用於從內存中刪除對象的複雜過程。就是這樣!:) 今天的課程到此結束。但是你不能只靠理論走得太遠,所以是時候回到解決任務上了!:)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION