你好!在今天的課程中,我們將詳細討論 Java 中的 PhantomReferences。這些是什麼類型的鏈接,為什麼它們被稱為“幻像”以及如何使用它們?正如您所記得的,Java 中有 4 種類型的連結:
-
StrongReference(我們在建立物件時建立的常規參考):
Cat cat = new Cat()
本例中的cat是強引用。
-
SoftReference(軟引用)。我們有一個關於這些連結的講座。
-
WeakReference(弱引用)。這裡也有關於他們的講座。
-
PhantomReference(幻像引用)。
SoftReference
,WeakReference
並PhantomReference
從類別繼承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
為幻像連結返回了對象。 第三,幻象引用的主要使用領域是從記憶體中刪除物件的複雜過程。就這樣!:) 今天的課程到此結束。但光靠理論並不能讓你走得太遠,所以是時候回去解決問題了!:)
GO TO FULL VERSION