你好!在今天的讨论中,我们将详细讨论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