Reference类简介

1 状态机

状态转移图

@startuml
[*] -> active: 新建
active -down-> inactive: 可达性改变且无注册队列
active -right-> pending: 可达性改变且有注册队列
pending -down-> enqueued: 成功加入队列
enqueued -left-> inactive: 从队列移除
inactive -left-> [*]
@enduml
Reference类简介_第1张图片
状态转移图

说明:

  1. active。此状态需要收集器进行特殊处理。当收集器检测到referent的可达性发生改变,它会将实例的状态改为为pendinginactive,这取决于实例在创建时是否已注册到某个队列。有注册队列情况,实例会添加到pending-Reference列表中,等待入队。新建引用实例是活动状态。
  2. pending。表示pending-Reference列表的元素,等待被Reference-handler处理并入队。未注册引用实例不会有此状态。。
  3. enqueued。表示在注册队列中的元素。当实例从ReferenceQueue中移除时,它将变为inactive。未注册引用实例不会有此状态。尽管Reference提供了入队方法,但是收集器执行的入队操作是直接执行的,而不是调用Reference的入队方法
  4. inactive。终止态,不会在改变。

状态编码
引用的状态是通过不同字段值共同体现的。

状态 queue next discovered
active 有注册队列时ReferenceQueue实例
或者 未注册队列时ReferenceQueue.NULL
NULL 下一个discovered列表元素
或者如果是队尾元素则是this
pending 引用所注册的ReferenceQueue实例 this 下一个pending列表元素
或者如果是队尾元素则是NULL
enqueued ReferenceQueue.ENQUEUED 下一个入队的实例
或者如果是队尾元素则是this
NULL
inactive ReferenceQueue.NULL this NULL

如何判断引用状态是否是active?
基于以上编码关系,垃圾收集器只需要对next字段进行判断,决定是不是需要进行特殊处理。规则如下:如果next字段为NULL,那么实例是active的;否则,收集器将会按照普通情况处理。

discovered字段的用途?
当引用状态为active时,为了保证并发收集器能够发现下一个active引用,同时不影响应用线程对这些active引用执行enqueue操作,收集器使用了discovered字段记录了下一个active引用。
当引用状态为pending时,discovered字段还记录了pending列表中的下一个引用。

引用队列的用途?
如果程序需要感知对象可达性的变化时,那么要在创建引用对象时传入注册队列。当垃圾收集器发现referent可达性发生变化时,会将referent的引用加入到注册队列中。此时,引用处于enqueued状态。程序可以通过阻塞轮询的方式,从队列中移除引用。[2]

已注册引用和引用注册队列的关系是单向的。也就是说,引用注册队列不会跟踪已注册引用的状态。如果已注册引用的状态变为不可达,那么它永远不会进入引用注册队列。所以,程序需要保证referent对象的可达性。

如果保证referent的可达性呢?
一种方式,使用单独的线程轮询,并从队列中删除引用对象,然后对其进行处理。
另一种方式,在执行操作引用时进行检查(lazy)。
例如,使用弱引用实现弱键的哈希表,可以在每次访问时轮询其引用队列。这就是WeakHashMap类的工作原理。由于ReferenceQueue.poll方法只检查内部数据结构,因此,将为哈希表访问方法增加很少的开销。

引用是何时入队的?
收集器在将软引用和弱引用加入注册队列(如果有的话)之前,自动清除软引用和弱引用。因此,软引用和弱引用不需要注册到队列中才能发挥作用,而虚引用则需要注册队列。虚引用对象会保持可达,除非虚引用被清除或者虚引用对象本身不可达。

public static void main(String[] args) {
    ReferenceQueue referenceQueue = new ReferenceQueue();
    PhantomReference phantomReference = // 如果不声明本地变量,在gc后将会入队
            new PhantomReference<>(new Object(), referenceQueue);
    System.gc();
    System.out.println("Object in queue: " + referenceQueue.poll());
}

2 tryHandlePending

处理pending列表中的引用对象。
参考如下代码,可见处理过程中,体现了这样一点。discovered字段记录这pending列表中的下一个元素。

Reference r;
Cleaner c;
synchronized (lock) {
    if (pending != null) {
        r = pending;
        // 'instanceof' might throw OutOfMemoryError sometimes
        // so do this before un-linking 'r' from the 'pending' chain...
        c = r instanceof Cleaner ? (Cleaner) r : null;
        // unlink 'r' from 'pending' chain
        pending = r.discovered;
        r.discovered = null;
    } else {
        // The waiting on the lock may cause an OutOfMemoryError
        // because it may try to allocate exception objects.
        if (waitForNotify) {
            lock.wait();
        }
        // retry if waited
        return waitForNotify;
    }
}
// Cleaner继承了PhantomReference,用于一些清理工作
if (c != null) {
    c.clean();
    return true;
}

ReferenceQueue q = r.queue;
// 有注册队列,未入队
if (q != ReferenceQueue.NULL) q.enqueue(r);

3 引用类型

类型 强引用 SoftReference WeakReference PhantomReference
定义 如果线程在不遍历任何引用对象的情况下访问某个对象,则该对象是强可达的。
新建的对象是线程强可达的。
如果对象不是强可达的,但可以通过遍历软引用来访问,则该对象是软可达的。 如果对象不是强、软可达的,但可以通过遍历弱引用访问,那么它就是弱可达的。
当对弱可达对象的弱引用被清除时,就要对该对象执行finalization过程。
如果一个对象不是强、软、弱可达的,那么它就是幻象可达的,对象已经被终止(finalized),但是幻象引用指向了它。
用途 普通引用 内存敏感的缓存 map 回收预清理(代替finalization机制)
垃圾回收 不可达时 收集器根据内存情况进行回收,保证在OOM之前回收 referent对象状态会正常经历finalizablefinalized,进而被回收 PhantomReference在收集器确定其referent不需要回收(maybe otherwise be reclaimed)时入队
强度 依次减弱
是否注册队列 - 否(一般) 否(一般)

当对象不属于上述四种可达方式时,成为不可达对象。不可达对象,需要进行回收。

软引用代码示例:

public static void main(String[] args) {
    Reference reference = new SoftReference(new Object());
    System.gc(); // 输出结果非null
    System.out.println("Object is: " + reference.get());
}

弱引用代码示例:

public static void main(String[] args) {
    Reference reference = new WeakReference(new Object());
    System.gc(); // 注释掉时,输出结果非null
    System.out.println("Object is: " + reference.get());
}

幻象引用代码示例:

public static void main(String[] args) {
    Reference reference = new PhantomReference(new Object(), null);
    System.gc(); // 无论是否注释掉,结果始终为null。因为重写了get方法
    System.out.println("Object is: " + reference.get());
}

问题

参考资料

  1. Reference
  2. package-summary.html#reachability

你可能感兴趣的:(Reference类简介)