Java ~ Reference ~ PhantomReference【总结】

前言


 文章

  • 相关系列:《Java ~ Reference【目录】》(持续更新)
  • 相关系列:《Java ~ Reference ~ PhantomReference【源码】》(学习过程/多有漏误/仅作参考/不再更新)
  • 相关系列:《Java ~ Reference ~ PhantomReference【总结】》(学习总结/最新最准/持续更新)
  • 相关系列:《Java ~ Reference ~ PhantomReference【问题】》(学习解答/持续更新)
  • 参考文献:《Java原生内存管理(native memory management)(4)-PhantomReference和WeakReference对比》
  • 参考文献:《虚引用所指向的对象到底什么时候被回收?》
  • 涉及内容:《Java ~ Reference ~ Cleaner【总结】》

一 概述


 简介

    PhantomReference(虚引用)类是Reference(引用)类的四大子类之一,只被虚引用持有的对象被称为虚可达(phantom reachable)对象。在Java中关于虚引用的定义是:虚引用等价于无引用,其无法对对象的生命周期产生任何影响,即对象的诞生、初始化、使用及回收完全与虚引用无关。这段描述与WeakReference(弱引用)类完全相同,以至于令人怀疑是不是复制黏贴的(反正我是复制黏贴的)…但实际上两者确实高度相似。

    虚引用类被设计专用于跟踪对象的GC状态,即判断对象是否已/会被GC回收。当GC发生时,如果发现某个该回收的对象存在虚引用,则会将该对象的虚引用加入到引用队列中,而当我们从引用队列中获取到了出队的虚引用,就可以判定其所指对象已/会被GC回收了(这种说法其实并不正确,但近似可以这样理解,具体会在下文讲述)。

    虚引用类有两个特点:一是必须与引用队列搭配使用,这与其本身的作用相关;二是其get()方法无法获取所指对象,即使其没有被GC回收。如此设计的目的是为了凸显其作用,虚引用类的设计者(Mark Reinhold:马克·莱因霍尔德)目的非常明显,他只希望虚引用类被用于其仅有的设计作用上(跟踪对象被垃圾回收的活动),除此以外没有任何使用它的理由。

    虚引用类可作为finalize()方法的替代方案。由于虚引用的存在,开发者能够知晓其所指对象是否已/会被GC回收,也因此可在所指对象被GC回收时执行自定义操作,这有点类似于方法finalize()的作用,因此虚引用类也被提倡作为finalize()方法的替代方案,毕竟finalize()方法本身存在着诸多缺陷…实际上弱引用类同样可作为finalize()方法的替代方案,但其自身特性决定了其可用于功能更加强大的场景。而虚引用类很专一…除了这事儿我啥都干不了,甚至还专门为此拓展了一个子类Cleaner(清洁工)类

 与弱引用的区别

    如果说在JDK9之前,虚引用还和弱引用还有着明显差别的话,那到了JDK9之后,基本上可以说两者已经完全相似了(该知识点会在下文详述)。两者都不会对所指对象的生命周期产生影响,并且都会在所指对象被GC回收后将引用加入引用队列中(如果弱引用注册了引用队列的话),因此弱引用类也完全可以达到与虚引用类相同的作用,那是否可以考虑将两者的其中之一删除呢?答案是不能。因为两者还是有一定区别的。

    虚引用类的get()方法永远返回null。虚引用类重写了get()方法,不论所指对象是否已/会被GC回收,该方法都会固定返回null。如此行为的目的是为了避免开发者通过虚引用影响所指对象的GC状态,即防止所指对象断开所有强引用后,开发者通过虚引用的get()方法获取到该所指对象并与GC Roots重新建立直接/间接的引用关系而使其复活。而弱引用类(以及其它引用类)则是可以做到这一点的,因为GC何时断开引用与其所指对象关联这件事情非常玄学,有时即使引用已经被加入引用队列,也依然可以通过get()方法获取到其所指对象,这也是我每次在描述“判断对象是否已/会被GC回收”时都会添加“会”字描述的原因。同时,由于无法获取所指对象,虚引用类也无法代替弱引用类在某些场景中发挥的作用,最典型的就是WeakHashMap(弱哈希映射)类,如果使用虚引用类来代替弱引用类,那该如何获取具体的键对象呢?

    虚引用类的入队时间与弱引用类不同。引用的入队时间与Finalization(终结)机制有关,即与所指对象的finalize()方法的调用有关。对于弱引用来说,其加入引用队列的时间在finalize()方法调用之前,即弱引用的处理优先级大于FinalReference(终引用),这在一定程度上会延长所指对象被GC回收前的停留时间,这就为其的复活提供了更大的可能。而虚引用的入队时间则在finalize()方法调用之后,即虚引用的处理优先级小于中引用,因此基本可以认为虚引用在入队时所指对象已经被GC回收了(而且实际上对虚引用讨论入队时间没有意义,因为你无法通过虚引用获取所指对象)。

二 使用


 创建

    虚引用类只有一个构造方法,该构造方法会同时指定所指对象及引用队列,这是由于虚引用在设计上被专用于跟踪所指对象的GC状态,因此其必须搭配引用队列才可使用。

  • public PhantomReference(T referent, ReferenceQueue q) —— 创建指定所指对象及注册引用队列的虚引用。

 方法

    虚引用类没有定义新方法,其所有方法皆继承/实现自引用抽象类。

  • public T get() —— 获取 —— 获取当前虚引用的所指对象,该方法会固定返回null。
        虚引用类重写了get()方法,不论所指对象是否已/会被GC回收,该方法都会固定返回null。如此行为的目的是为了避免开发者通过虚引用影响所指对象的GC状态,即防止所指对象断开所有强引用后,开发者通过虚引用的get()方法获取到该所指对象与GC Roots重新建立引用关系而使其复活,具体源码如下:
/**
 * Returns this reference object's referent.  Because the referent of a
 * phantom reference is always inaccessible, this method always returns
 * null.
 *
 * @return null
 */
@Override
public T get() {
    // 永远返回null。
    return null;
}
  • public void clear() —— 清除 —— 清除当前虚引用的所指对象(即断开两者的引用关系),并不会将当前虚引用加入到注册引用队列中。该方法专为开发者提供,GC线程不会调用该方法断开当前虚引用与其所指对象的关联。

  • public boolean isEnqueued() —— 是否入队 —— 判断当前虚引用是否已加入注册引用队列,是则返回true;否则返回false。引用加入注册引用队列时会将自身注册的引用队列替换为“入队”引用队列,这是一个在引用队列类内部创建的全局静态引用队列,被作为引用加入注册引用队列的标志位来使用。因此判断当前引用是否加入注册引用队列无需遍历注册引用队列,直接判断注册引用队列是否是“入队”引用队列即可。

  • public boolean enqueue() —— 入队 —— 将当前虚引用加入注册引用队列中,成功返回true;否则返回false。该方法底层调用引用队列类的enqueue(Reference r) 方法实现。该方法专为开发者提供,“引用处理器”线程不会调用该方法将当前虚引用加入注册引用队列。

三 回收时机


    虚可达对象在何时被GC回收是虚引用的难点,且根据不同版本在机制上也有所区别。在JDK8及之前,虽然GC可能已经将虚可达/所指对象的虚引用加入引用队列中,但此时的虚可达/所指对象实际上并没有被GC回收,也就是说,虚引用会阻止虚可达/所指对象被GC回收。那虚可达/所指对象具体会在什么时候被GC回收呢?在源码中有这样一段注解。

Unlike soft and weak references, phantom references are not
automatically cleared by the garbage collector as they are enqueued.
与软引用和弱引用不同,虚引用在进入队列时不会被GC自动清除。

An object that is reachable via phantom references will remain so
until all such references are cleared or themselves become unreachable.
通过虚引用可访问的对象将保持这种状态,直到所有此类引用被清除或其本身变得不可访问。

    在JDK8及之前,虚可达/所指对象并不会因为其虚引用被加入到引用队列中而被GC回收。其具体的回收时机有二:一是虚引用调用了clear()方法,断开了与虚可达/所指对象的引用关联,此时的虚可达/所指对象变成了无引用(不可达)对象,则自然会被GC回收;二是虚引用被判断为可回收,则与之关联的虚可达/所指对象自然也会被GC回收。

    而从JDK9开始,GC会将虚可达/所指对象回收后再将虚引用加入引用队列中,即只要虚引用存在于引用队列中便意味着虚可达/所指对象已/会被GC回收…虽然开发者依然要通过出队才能知道其是否存在。从机制上看,显然是JDK9的机制符合标准流程,那为什么会有这么大的区别呢?其实也没有深层次的理由,早期由于涉及到专利的原因没有办法这么写,现在专利期过了自然也就更换了更优的写法。

你可能感兴趣的:(Java,java,jvm,开发语言,Reference,Phantom)