你好!在今天的课程中,我们将详细讨论Java中的幻像引用。这些是什么类型的链接,为什么它们被称为“幻像”以及如何使用它们?正如您所记得的,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!!!");
}
}
我们在创建对象时故意“加载”数据(为每个对象添加 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("Очистка фантомной ссылки! Удаление 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