你好!在今天的討論中,我們將詳細討論Java中的“幻象引用”(PhantomReference)。這些是什麼參考資料?為什麼它們被稱為“幻影引用”?它們是如何使用的?你會記得,Java 有 4 種引用:
-
StrongReference(我們在創建對象時創建的普通引用):
Cat cat = new Cat()
在此示例中,cat是一個強引用。
-
SoftReference(軟引用)。我們有關於此類參考的教訓。
-
WeakReference(弱引用)。這裡也有關於他們的教訓。
-
PhantomReference(虛引用)。
-
get() — 返回引用的對象;
- clear() — 刪除對對象的引用。
finalize()
對象的方法已被重寫,它被調用。或者也許它沒有被調用——這完全取決於你是否幸運。您可能還記得那finalize()
是善變的 :) 在垃圾收集器的第二遍中,對像被刪除並釋放內存。垃圾收集器不可預知的行為給我們帶來了很多問題。我們不知道垃圾收集器何時開始運行。我們不知道該finalize()
方法是否會被調用。finalize()
另外,可以在執行對象的方法時創建對對象的強引用,在這種情況下根本不會刪除該對象。對於對可用內存有大量要求的程序,這很容易導致OutOfMemoryError
. 所有這些都促使我們使用幻象引用. 事實上,這會改變垃圾收集器的行為。如果對像只有虛引用,則:
-
它的finalize()方法被調用(如果它被覆蓋)
-
如果在finalize()方法完成後沒有任何變化並且對象仍然可以被刪除,那麼對該對象的幻影引用被放置在一個特殊的隊列中:ReferenceQueue。
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 。第三,幻象引用主要用於從內存中刪除對象的複雜過程。就是這樣!:) 今天的課程到此結束。但是你不能只靠理論走得太遠,所以是時候回到解決任務上了!:)
GO TO FULL VERSION