CodeGym /Java 博客 /随机的 /Java 中的虚引用
John Squirrels
第 41 级
San Francisco

Java 中的虚引用

已在 随机的 群组中发布
你好!在今天的讨论中,我们将详细讨论Java中的“幻象引用”(PhantomReference)。这些是什么参考资料?为什么它们被称为“幻影引用”?它们是如何使用的?你会记得,Java 有 4 种引用:
  1. StrongReference(我们在创建对象时创建的普通引用):

    Cat cat = new Cat()

    在此示例中,cat是一个强引用。

  2. SoftReference(软引用)。我们有关于此类参考的教训

  3. WeakReference(弱引用)。这里也有关于他们的教训。

  4. PhantomReference(虚引用)。

最后三个是具有类型参数的泛型类型(例如,SoftReference<Integer>WeakReference<MyClass>)。SoftReference 、WeakReferencePhantomReference继承自Reference类。使用这些类时最重要的方法是:
  • get() — 返回引用的对象;

  • clear() — 删除对对象的引用。

您还记得有关SoftReferenceWeakReference的课程中​​的这些方法。重要的是要记住,它们对不同类型的引用有不同的作用。今天我们不深入探讨前三种类型。相反,我们将讨论虚引用。我们将触及其他类型的引用,但仅涉及它们与虚引用的区别。我们走吧!:) 首先,为什么我们需要虚引用?如您所知,垃圾收集器 (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("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 。第三,幻象引用主要用于从内存中删除对象的复杂过程。就是这样!:) 今天的课程到此结束。但是你不能只靠理论走得太远,所以是时候回到解决任务上了!:)
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION