JavaRush /Java Blog /Random-TW /Java 中的幻象引用

Java 中的幻象引用

在 Random-TW 群組發布
你好!在今天的課程中,我們將詳細討論 Java 中的 PhantomReferences。這些是什麼類型的鏈接,為什麼它們被稱為“幻像”以及如何使用它們?正如您所記得的,Java 中有 4 種類型的連結:
  1. StrongReference(我們在建立物件時建立的常規參考):

    Cat cat = new Cat()

    本例中的cat是強引用。

  2. SoftReference(軟引用)。我們有一個關於這些連結的講座

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

  4. PhantomReference(幻像引用)。

最後三種類型的物件是類型化的(例如,SoftReference<Integer>WeakReference<MyClass>)。類SoftReferenceWeakReferencePhantomReference從類別繼承Reference。使用這些類別時最重要的方法是:
  • get()- 傳回此連結所引用的物件;

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

SoftReference您從關於和 的講座中記住了這些方法WeakReference。重要的是要記住,它們對於不同類型的連結的工作方式有所不同。今天我們不會詳細討論前三種類型,但會討論幻像連結。我們還將討論其他類型的鏈接,但僅限於我們將討論幻像鏈接與它們有何不同的部分。去!:) 讓我們先了解為什麼我們需要幻像連結。如您所知,垃圾收集器(Garbage Collector 或 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("У an object TestClass вызван метод finalize!!!");
   }
}
我們在創建物件時故意「載入」資料(為每個物件添加 5,000 萬個「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("Очистка фантомной ссылки! Удаление an object из памяти!");
       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("Поток, отслеживающий очередь, стартовал!");
       Reference ref = null;

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

           try {
               Thread.sleep(50);
           }

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

       //How только в очереди появилась фантомная link - очистить ее
       ((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("Вызывается сборка мусора!");

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

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

       Thread.sleep(5000);

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

       System.gc();
   }
}
控制台輸出: ref = MyPhantomReference@4554617c 監控佇列的執行緒已啟動!垃圾收集被稱為!TestClass 物件的 Finalize 方法已被呼叫!!!ref = MyPhantomReference@4554617c 正在呼叫垃圾收集!清理虛假連結!從記憶體中刪除一個物件! 我們在這裡看到什麼?一切都按照我們的計劃發生了!我們的物件類別有一個被重寫的方法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