JavaRush /Java 博客 /Random-ZH /Java 中的幻像引用

Java 中的幻像引用

已在 Random-ZH 群组中发布
你好!在今天的课程中,我们将详细讨论Java中的幻像引用。这些是什么类型的链接,为什么它们被称为“幻像”以及如何使用它们?正如您所记得的,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!!!");
   }
}
我们在创建对象时故意“加载”数据(为每个对象添加 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为幻像链接返回了对象。 第三,幻像引用的主要使用领域是从内存中删除对象的复杂过程。就这样!:) 今天的课程到此结束。但光靠理论并不能让你走得太远,所以是时候回去解决问题了!:)
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION