深刻理解JDK中的Reference原理和源码实现

前提

这篇文章主要基于JDK11的源码和最近翻看的《深刻理解Java虚拟机-2nd》一书的部份内容,对JDK11中的Reference(引用)作一些总结。值得注意的是,经过笔者对比一下JDK11和JDK8对于java.lang.ref包的相关实现,发现代码变化比较大,所以本文的源码分析可能并不适合于JDK11以外的JDK版本java

Reference的简介和分类

在JDK1.2以前,Java中的引用的定义是十分传统的:若是reference类型的数据中存储的数值表明的是另外一块内存的起始地址,就称这块内存表明着一个引用。在这种定义之下,一个对象只有被引用和没有被引用两种状态。算法

实际上,咱们更但愿存在这样的一类对象:当内存空间还足够的时候,这些对象可以保留在内存空间中;若是当内存空间在进行了垃圾收集以后仍是很是紧张,则能够抛弃这些对象。基于这种特性,能够知足不少系统的缓存功能的使用场景。数组

java.lang.ref包是JDK1.2引入的,包结构和类分布以下:缓存

- java.lang.ref
  - Cleaner.class
  - Finalizer.class
  - FinalizerHistogram.class
  - FinalReference.class
  - PhantomReference.class
  - Reference.class
  - ReferenceQueue.class
  - SoftReference.classs
  - WeakReference.class

引入此包的做用是对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种类型的引用,还有一种比较特殊的引用是析构引用(Final Reference),它是一种特化的虚引用。四种引用的强度按照下面的次序依次减弱:安全

StrongReference > SoftReference > WeakReference > PhantomReference

值得注意的是:app

  • 强引用没有对应的类型表示,也就是说强引用是广泛存在的,如Object object = new Object();
  • 软引用、弱引用和虚引用都是java.lang.ref.Reference的直接子类。
  • 直到JDK11为止,只存在四种引用,这些引用是由JVM建立,所以直接继承java.lang.ref.Reference建立自定义的引用类型是无效的,可是能够直接继承已经存在的引用类型,如java.lang.ref.Cleaner就是继承自java.lang.ref.PhantomReference
  • 特殊的java.lang.ref.Reference的子类java.lang.ref.FinalReferenceObject#finalize()有关,java.lang.ref.Finalizerjava.lang.ref.FinalReference子类,下文会详细分析这些内容。

Reference

Reference就是引用,对JVM的垃圾收集活动敏感(固然,强引用可能对垃圾收集活动是不敏感的),Reference的继承关系或者实现是由JDK定制,引用实例是由JVM建立,因此自行继承Reference实现自定义的引用类型是无心义的,可是能够继承已经存在的引用类型,如SoftReference等。Reference类文件的注释也比较简短,可是方法和变量的注释十分详细,特别是用图表代表了状态跃迁的过程,这里先看类文件头注释:ide

Abstract base class for reference objects. This class defines the operations common to all reference objects. Because reference objects are implemented in close cooperation with the garbage collector, this class may not be subclassed directly.函数

翻译一下大意是:Reference是全部引用对象的基类。这个类定义了全部引用对象的通用操做。由于引用对象是与垃圾收集器紧密协做而实现的,因此这个类可能不能直接子类化。工具

Reference的状态集合

Reference源码中并不存在一个成员变量用于描述Reference的状态,它是经过组合判断referent、discovered、queue、next成员的存在性或者顺序"拼凑出"对应的状态,注释中描述以下:oop

一个引用对象能够同时存在两种状态:
- 第一组状态:"active", "pending", or "inactive"
- 第二组状态:"registered", "enqueued", "dequeued", or "unregistered"

Active:

当前引用实例处于Active状态,会收到垃圾收集器的特殊处理。在垃圾收集器检测到referent的可达性已更改成适当状态以后的某个时间,垃圾收集器会"通知"当前引用实例改变其状态为"pending"或者"inactive"。此时的判断条件是:referent != null; discovered = null或者实例位于GC的discovered列表中。

Pending:

当前的引用实例是pending-Reference列表的一个元素,等待被ReferenceHandler线程处理。pending-Reference列表经过应用实例的discovered字段进行关联。此时的判断条件是:referent = null; discovered = pending-Reference列表中的下一个元素

Inactive:

当前的引用实例处于非Active和非Pending状态。此时的判断条件是:referent = null (同时discovered = null)

Registered:

当前的引用实例建立的时候关联到一个引用队列实例,可是引用实例暂未加入到队列中。此时的判断条件是:queue = 传入的ReferenceQueue实例

Enqueued:

当前的引用实例已经添加到和它关联的引用队列中可是还没有移除(remove),也就是调用了ReferenceQueue.enqueued()后的Reference实例就会处于这个状态。此时的判断条件是:queue = ReferenceQueue.ENQUEUE; next = 引用列表中的下一个引用实例,或者若是当前引用实例是引用列表中的最后一个元素,则它会进入Inactive状态

Dequeued:

当前的引用实例曾经添加到和它关联的引用队列中而且已经移除(remove)。此时的判断条件是:queue = ReferenceQueue.NULL; next = 当前的引用实例

Unregistered:

当前的引用实例不存在关联的引用队列,也就是建立引用实例的时候传入的queue为null。此时的判断条件是:queue = ReferenceQueue.NULL

状态跃迁的时序图以下:

* Initial states:
     *   [active/registered]
     *   [active/unregistered] [1]
     *
     * Transitions:
     *                            clear
     *   [active/registered]     ------->   [inactive/registered]
     *          |                                 |
     *          |                                 | enqueue [2]
     *          | GC              enqueue [2]     |
     *          |                -----------------|
     *          |                                 |
     *          v                                 |
     *   [pending/registered]    ---              v
     *          |                   | ReferenceHandler
     *          | enqueue [2]       |--->   [inactive/enqueued]
     *          v                   |             |
     *   [pending/enqueued]      ---              |
     *          |                                 | poll/remove
     *          | poll/remove                     |
     *          |                                 |
     *          v            ReferenceHandler     v
     *   [pending/dequeued]      ------>    [inactive/dequeued]
     *
     *
     *                           clear/enqueue/GC [3]
     *   [active/unregistered]   ------
     *          |                      |
     *          | GC                   |
     *          |                      |--> [inactive/unregistered]
     *          v                      |
     *   [pending/unregistered]  ------
     *                           ReferenceHandler
     *
     * Terminal states:
     *   [inactive/dequeued]
     *   [inactive/unregistered]
     *
     * Unreachable states (because enqueue also clears):
     *   [active/enqeued]
     *   [active/dequeued]
     *
     * [1] Unregistered is not permitted for FinalReferences.
     *
     * [2] These transitions are not possible for FinalReferences, making
     * [pending/enqueued] and [pending/dequeued] unreachable, and
     * [inactive/registered] terminal.
     *
     * [3] The garbage collector may directly transition a Reference
     * from [active/unregistered] to [inactive/unregistered],
     * bypassing the pending-Reference list.

注释中还强调了几点:

  • 初始化状态:[active/registered][active/unregistered](这种状况只限于FinalReferences)
  • 终结状态:[inactive/dequeued][inactive/unregistered]
  • 不可能出现的状态:[active/enqeued][active/dequeued]

上面的图看起来可能比较抽象,ReferenceHandler实际上是Reference中静态代码块中初始化的线程实例,主要做用是:处理pending状态的引用实例,使它们入队列并走向[inactive/dequeued]状态。另外,上面的线框图是分两部分,其中上半部分是使用了ReferenceQueue,后半部分是没有使用ReferenceQueue(或者说使用了ReferenceQueue.NULL)。这里尝试用PPT画一下简化的状态跃迁图:

Reference源码分析

先看Reference的构造函数和成员变量:

public abstract class Reference<T> {
   private T referent;
   volatile ReferenceQueue<? super T> queue;
   volatile Reference next;
   private transient Reference<T> discovered;

   Reference(T referent) {
        this(referent, null);
   }

   Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
   }
}

构造描述

构造函数依赖于一个泛型的referent成员以及一个ReferenceQueue<? super T>的队列,若是ReferenceQueue实例为null,则使用ReferenceQueue.NULL

成员变量描述

  • referent:Reference保存的引用指向的对象,下面直接称为referent。
// GC特殊处理的对象
private T referent;         /* Treated specially by GC */
  • queue:Reference对象关联的队列,也就是引用队列,对象若是即将被垃圾收集器回收,此队列做为通知的回调队列,也就是当Reference实例持有的对象referent要被回收的时候,Reference实例会被放入引用队列,那么程序执行的时候能够从引用队列获得或者监控相应的Reference实例。
/* The queue this reference gets enqueued to by GC notification or by
     * calling enqueue().
     *
     * When registered: the queue with which this reference is registered.
     *        enqueued: ReferenceQueue.ENQUEUE
     *        dequeued: ReferenceQueue.NULL
     *    unregistered: ReferenceQueue.NULL
     */
    volatile ReferenceQueue<? super T> queue;
  • next:下一个Reference实例的引用,Reference实例经过此构造单向的链表。
/* The link in a ReferenceQueue's list of Reference objects.
     *
     * When registered: null
     *        enqueued: next element in queue (or this if last)
     *        dequeued: this (marking FinalReferences as inactive)
     *    unregistered: null
     */
    @SuppressWarnings("rawtypes")
    volatile Reference next;
  • discovered:注意这个属性由transient修饰,基于状态表示不一样链表中的下一个待处理的对象,主要是pending-reference列表的下一个元素,经过JVM直接调用赋值。
/* When active:  next element in a discovered reference list maintained by GC (or this if last)
*     pending:   next element in the pending list (or null if last)
*     otherwise:   NULL
*/
transient private Reference<T> discovered;  /* used by VM */

实例方法(和ReferenceHandler线程不相关的方法)

// 获取持有的referent实例
@HotSpotIntrinsicCandidate
public T get() {
     return this.referent;
}

// 把持有的referent实例置为null
public void clear() {
     this.referent = null;
}

// 判断是否处于enqeued状态
public boolean isEnqueued() {
     return (this.queue == ReferenceQueue.ENQUEUED);
}

// 入队参数,同时会把referent置为null
public boolean enqueue() {
     this.referent = null;
     return this.queue.enqueue(this);
}

// 覆盖clone方法而且抛出异常,也就是禁止clone
@Override
protected Object clone() throws CloneNotSupportedException {
     throw new CloneNotSupportedException();
}

// 确保给定的引用实例是强可达的
@ForceInline
public static void reachabilityFence(Object ref) {
}

ReferenceHandler线程

ReferenceHandler线程是由Reference静态代码块中创建而且运行的线程,它的运行方法中依赖了比较多的本地(native)方法,ReferenceHandler线程的主要功能是处理pending链表中的引用对象:

// ReferenceHandler直接继承于Thread覆盖了run方法
    private static class ReferenceHandler extends Thread {
        
        // 静态工具方法用于确保对应的类型已经初始化
        private static void ensureClassInitialized(Class<?> clazz) {
            try {
                Class.forName(clazz.getName(), true, clazz.getClassLoader());
            } catch (ClassNotFoundException e) {
                throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
            }
        }

        static {
            // 确保Cleaner这个类已经初始化
            // pre-load and initialize Cleaner class so that we don't
            // get into trouble later in the run loop if there's
            // memory shortage while loading/initializing it lazily.
            ensureClassInitialized(Cleaner.class);
        }

        ReferenceHandler(ThreadGroup g, String name) {
            super(g, null, name, 0, false);
        }
        
        // 注意run方法是一个死循环执行processPendingReferences
        public void run() {
            while (true) {
                processPendingReferences();
            }
        }
    }

    /* 原子获取(后)而且清理VM中的pending引用链表
     * Atomically get and clear (set to null) the VM's pending-Reference list.
     */
    private static native Reference<Object> getAndClearReferencePendingList();

    /* 检验VM中的pending引用对象链表是否有剩余元素
     * Test whether the VM's pending-Reference list contains any entries.
     */
    private static native boolean hasReferencePendingList();

    /* 等待直到pending引用对象链表不为null,此方法阻塞的具体实现又VM实现
     * Wait until the VM's pending-Reference list may be non-null.
     */
    private static native void waitForReferencePendingList();

    // 锁对象,用于控制等待pending对象时候的加锁和开始处理这些对象时候的解锁
    private static final Object processPendingLock = new Object();
    // 正在处理pending对象的时候,这个变量会更新为true,处理完毕或者初始化状态为false,用于避免重复处理或者重复等待
    private static boolean processPendingActive = false;

    // 这个是死循环中的核心方法,功能是处理pending链表中的引用元素
    private static void processPendingReferences() {
        // Only the singleton reference processing thread calls
        // waitForReferencePendingList() and getAndClearReferencePendingList().
        // These are separate operations to avoid a race with other threads
        // that are calling waitForReferenceProcessing().
        // (1)等待
        waitForReferencePendingList();
        Reference<Object> pendingList;
        synchronized (processPendingLock) {
            // (2)获取并清理,标记处理中状态
            pendingList = getAndClearReferencePendingList();
            processPendingActive = true;
        }
        // (3)经过discovered(下一个元素)遍历pending链表进行处理
        while (pendingList != null) {
            Reference<Object> ref = pendingList;
            pendingList = ref.discovered;
            ref.discovered = null;
            // 若是是Cleaner类型执行执行clean方法而且对锁对象processPendingLock进行唤醒全部阻塞的线程
            if (ref instanceof Cleaner) {
                ((Cleaner)ref).clean();
                // Notify any waiters that progress has been made.
                // This improves latency for nio.Bits waiters, which
                // are the only important ones.
                synchronized (processPendingLock) {
                    processPendingLock.notifyAll();
                }
            } else {
                // 非Cleaner类型而且引用队列不为ReferenceQueue.NULL则进行入队操做
                ReferenceQueue<? super Object> q = ref.queue;
                if (q != ReferenceQueue.NULL) q.enqueue(ref);
            }
        }
        // (4)当次循环结束以前再次唤醒锁对象processPendingLock上阻塞的全部线程
        // Notify any waiters of completion of current round.
        synchronized (processPendingLock) {
            processPendingActive = false;
            processPendingLock.notifyAll();
        }
    }

ReferenceHandler线程启动的静态代码块以下:

static {
        // ThreadGroup继承当前执行线程(通常是主线程)的线程组
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        // 建立线程实例,命名为Reference Handler,配置最高优先级和后台运行(守护线程),而后启动
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        /* If there were a special system-only priority greater than
         * MAX_PRIORITY, it would be used here
         */
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();
        // 注意这里覆盖了全局的jdk.internal.misc.JavaLangRefAccess实现
        // provide access in SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean waitForReferenceProcessing()
                throws InterruptedException{
                return Reference.waitForReferenceProcessing();
            }

            @Override
            public void runFinalization() {
                Finalizer.runFinalization();
            }
        });
    }

    // 若是正在处理pending链表中的引用对象或者监测到VM中的pending链表中还有剩余元素则基于锁对象processPendingLock进行等待
    private static boolean waitForReferenceProcessing()
        throws InterruptedException{
        synchronized (processPendingLock) {
            if (processPendingActive || hasReferencePendingList()) {
                // Wait for progress, not necessarily completion.
                processPendingLock.wait();
                return true;
            } else {
                return false;
            }
        }
    }

因为ReferenceHandler线程是Reference的静态代码建立的,因此只要Reference这个父类被初始化,该线程就会建立和运行,因为它是守护线程,除非JVM进程终结,不然它会一直在后台运行(注意它的run()方法里面使用了死循环)。

ReferenceQueue

JDK中对ReferenceQueue的文档描述是比较少的,类文件只有一句简单的注释:

Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected.

翻译一下大意为:引用队列,垃圾收集器在检测到适当的可达性更改后将已注册的引用对象追加到该队列。

从源码上看,实际上ReferenceQueue只是名义上的引用队列,它只保存了Reference链表的头(head)节点,而且提供了出队、入队和移除等操做,而Reference实际上自己提供单向链表的功能,也就是Reference经过成员属性next构建单向链表,而链表的操做是委托给ReferenceQueue完成,这里的逻辑有点绕ReferenceQueue的源码比较少,这里全量贴出标注一下注释:

public class ReferenceQueue<T> {

    public ReferenceQueue() { }
    
    // 内部类Null类继承自ReferenceQueue,覆盖了enqueue方法返回false
    private static class Null extends ReferenceQueue<Object> {
        boolean enqueue(Reference<?> r) {
            return false;
        }
    }
    
    // ReferenceQueue.NULL和ReferenceQueue.ENQUEUED都是内部类Null的新实例
    static final ReferenceQueue<Object> NULL = new Null();
    static final ReferenceQueue<Object> ENQUEUED = new Null();
    
    // 静态内部类,做为锁对象
    private static class Lock { };
    // 锁实例
    private final Lock lock = new Lock();
    // 引用链表的头节点
    private volatile Reference<? extends T> head;
    // 引用队列长度,入队则增长1,出队则减小1
    private long queueLength = 0;  

    // 入队操做,只会被Reference实例调用
    boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
        // 加锁
        synchronized (lock) {
            // Check that since getting the lock this reference hasn't already been
            // enqueued (and even then removed)
            // 若是引用实例持有的队列为ReferenceQueue.NULL或者ReferenceQueue.ENQUEUED则入队失败返回false
            ReferenceQueue<?> queue = r.queue;
            if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            assert queue == this;
            // Self-loop end, so if a FinalReference it remains inactive.
            // 若是链表没有元素,则此引用实例直接做为头节点,不然把前一个引用实例做为下一个节点
            r.next = (head == null) ? r : head;
            // 当前实例更新为头节点,也就是每个新入队的引用实例都是做为头节点,已有的引用实例会做为后继节点
            head = r;
            // 队列长度增长1
            queueLength++;
            // Update r.queue *after* adding to list, to avoid race
            // with concurrent enqueued checks and fast-path poll().
            // Volatiles ensure ordering.
            // 当前引用实例已经入队,那么它自己持有的引用队列实例置为ReferenceQueue.ENQUEUED
            r.queue = ENQUEUED;
            // 特殊处理FinalReference,VM进行计数
            if (r instanceof FinalReference) {
                VM.addFinalRefCount(1);
            }
            // 唤醒全部等待的线程
            lock.notifyAll();
            return true;
        }
    }

    // 引用队列的poll操做,此方法必须在加锁状况下调用
    private Reference<? extends T> reallyPoll() {       /* Must hold lock */
        Reference<? extends T> r = head;
        if (r != null) {
            r.queue = NULL;
            // Update r.queue *before* removing from list, to avoid
            // race with concurrent enqueued checks and fast-path
            // poll().  Volatiles ensure ordering.
            @SuppressWarnings("unchecked")
            Reference<? extends T> rn = r.next;
            // Handle self-looped next as end of list designator.
            // 更新next节点为头节点,若是next节点为自身,说明已经走过一次出队,则返回null
            head = (rn == r) ? null : rn;
            // Self-loop next rather than setting to null, so if a
            // FinalReference it remains inactive.
            // 当前头节点变动为环状队列,考虑到FinalReference尚为inactive和避免重复出队的问题
            r.next = r;
            // 队列长度减小1
            queueLength--;
            // 特殊处理FinalReference,VM进行计数
            if (r instanceof FinalReference) {
                VM.addFinalRefCount(-1);
            }
            return r;
        }
        return null;
    }

    // 队列的公有poll操做,主要是加锁后调用reallyPoll
    public Reference<? extends T> poll() {
        if (head == null)
            return null;
        synchronized (lock) {
            return reallyPoll();
        }
    }
    
    // 移除引用队列中的下一个引用元素,实际上也是依赖于reallyPoll的Object提供的阻塞机制
    public Reference<? extends T> remove(long timeout)
        throws IllegalArgumentException, InterruptedException{
        if (timeout < 0) {
            throw new IllegalArgumentException("Negative timeout value");
        }
        synchronized (lock) {
            Reference<? extends T> r = reallyPoll();
            if (r != null) return r;
            long start = (timeout == 0) ? 0 : System.nanoTime();
            for (;;) {
                lock.wait(timeout);
                r = reallyPoll();
                if (r != null) return r;
                if (timeout != 0) {
                    long end = System.nanoTime();
                    timeout -= (end - start) / 1000_000;
                    if (timeout <= 0) return null;
                    start = end;
                }
            }
        }
    }
    
    // remove,超时时间为0,实际上就是lock.wait(0)就是永久阻塞直至唤醒
    public Reference<? extends T> remove() throws InterruptedException {
        return remove(0);
    } 

    // foreach
    void forEach(Consumer<? super Reference<? extends T>> action) {
        for (Reference<? extends T> r = head; r != null;) {
            action.accept(r);
            @SuppressWarnings("unchecked")
            Reference<? extends T> rn = r.next;
            if (rn == r) {
                if (r.queue == ENQUEUED) {
                    // still enqueued -> we reached end of chain
                    r = null;
                } else {
                    // already dequeued: r.queue == NULL; ->
                    // restart from head when overtaken by queue poller(s)
                    r = head;
                }
            } else {
                // next in chain
                r = rn;
            }
        }
    }       
}

ReferenceQueue的源码十分简单,仍是从新提一下,它只存储了Reference链表的头节点,真正的Reference链表的全部节点是存储在Reference实例自己,经过属性next拼接的,ReferenceQueue提供了对Reference链表的入队、poll、remove等操做。

判断对象的可达性和对象是否存活

判断对象的可达性和对象是否存活是两个比较困难的问题,笔者C语言学得比较烂,不然会重点翻看一下JVM的实现,目前只能参考一些资料来讲明这个问题。

可达性算法

主流商用语言包括Java都是使用可达性分析(Reachability Analysis)算法来断定对象是否存活的。这个算法的基本思路是经过一系列的称为"GC Roots"(GC根集)的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC根集没有任何引用链相连(从图论的角度看,也就是从GC根集到这个对象是不可达的)时,则证实此对象是不可用的。不可用的对象"有机会"被断定为能够回收的对象。

在Java语言中,能够做为GC根集的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中常量引用的对象(在JDK1.8以后不存在方法区,也就是有多是metaspace中常量引用的对象)。
  • 本地方法栈中JNI(即通常常说的Native方法)引用的对象。

finalize函数

即便在可达性分析算法中断定为不可达的对象,也并不是必定会断定为能够被回收的"死亡"对象。一个对象断定为"死亡"至少须要经历两次标记的过程。

第一次标记:若是对象在进行可达性分析后发现没有与GC Roots相链接的引用链,那么它将会被第一次标记而且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。JVM会把如下两种状况认为对象没有必要执行finalize()方法:

  • 对象没有覆盖继承自Object类的finalize()方法。
  • 对象的finalize()方法已经被JVM调用过。

若是一个对象被断定为有必要执行finalize()方法,那么这个对象将会被放置在一个叫F-Queue的队列之中,而且稍后由一个优先级低的Finalizer线程去取该队列的元素,"尝试执行"元素的finalize()方法。这里之因此叫尝试执行是由于JVM会保证触发知足条件的对象的finalize()方法,可是并不承诺会等待它执行结束,这是由于:若是一个对象在执行finalize()方法耗时较长,甚至发生了死循环,将会致使F-Queue的队列中的其余元素永远处于等待状态,极端状况下有可能致使整个内存回收系统崩溃

finalize()方法是对象逃脱死亡命运的最后一次机会,由于稍后的GC将会对F-Queue队列中的对象进行第二次小规模的标记,若是对象在finalize()方法执行过程当中成功拯救本身--也就是对象自身从新与引用链的任何一个对象创建关联便可,最多见的就是把自身(this关键字)赋值给某个类变量或者对象的成员属性,那么在第二次小规模的标记时候将会把"自我拯救"成功的对象移出"即将回收"的集合。若是对象在finalize()方法执行过程当中没有"逃逸",那么它最终就会被回收。参考《深刻理解Java虚拟机-2nd》的"对象自我拯救的例子":

public class FinalizeEscapeGc {

    private static FinalizeEscapeGc SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("Yes,I am still alive :)");
    }

    public static void main(String[] args) throws Exception {
        SAVE_HOOK = new FinalizeEscapeGc();

        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("No,I am not alive :(");
        }
                // 下面的这段代码和上面的一致
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("No,I am not alive :(");
        }
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("FinalizeEscapeGc finalize invoke...");
        FinalizeEscapeGc.SAVE_HOOK = this;
    }
}
// 输出结果
FinalizeEscapeGc finalize invoke...
Yes,I am still alive :)
No,I am not alive :(

注意:

  • finalize()方法的错误使用有多是内存回收系统崩溃的根源,通常状况下谨慎思考是否真的须要覆盖此方法。
  • 任意一个对象只能经过finalize()方法自我拯救一次。

Finalizer守护线程

前面提到的Finalizer守护线程和F-Queue队列其实在JDK中有具体的实现类java.lang.ref.FinalizerF-Queue队列只是《深刻理解Java虚拟机-2nd》中的一个名词描述,实际上笔者没有找到相关的资料,这里咱们经过分析JDK和JVM相关的源码去理解这个F-Queue队列吧。先看java.lang.ref.Finalizer的源码,代码比较少全量贴出:

final class Finalizer extends FinalReference<Object> { /* Package-private; must be in
                                                          same package as the Reference
                                                          class */
    // Finalizer关联的ReferenceQueue,其实Finalizer是一个特殊的Reference实现
    private static ReferenceQueue<Object> queue = new ReferenceQueue<>();

    /** Head of doubly linked list of Finalizers awaiting finalization. */
    // 等待finalization的全部Finalizer实例链表的头节点,这里称此链表为unfinalized链表
    private static Finalizer unfinalized = null;

    /** Lock guarding access to unfinalized list. */
    // unfinalized链表的锁,静态final,全局的锁实例
    private static final Object lock = new Object();
    
    // 中间变量,分别记录unfinalized链表中当前执行元素的下一个节点和前一个节点
    private Finalizer next, prev;

    private Finalizer(Object finalizee) {
        super(finalizee, queue);
        // push onto unfinalized
        // 这里主要是更新unfinalized链表的头节点,新增的元素老是会变成头节点
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }

    static ReferenceQueue<Object> getQueue() {
        return queue;
    }

    /* Invoked by VM */ 这个方法由JVM激活,也就是链表的元素入队是由JVM控制的,见下文分析
    static void register(Object finalizee) {
        new Finalizer(finalizee);
    }

    private void runFinalizer(JavaLangAccess jla) {
        synchronized (lock) {
            // 当前元素已经处理过,直接返回
            if (this.next == this)      // already finalized
                return;
            // unlink from unfinalized
            // 下面的逻辑是当前须要执行的元素从链表中移除,而且更新prev和next的值,至关于重建链表的部分节点
            if (unfinalized == this)
                unfinalized = this.next;
            else
                this.prev.next = this.next;
            if (this.next != null)
                this.next.prev = this.prev;
            this.prev = null;
            this.next = this;           // mark as finalized
        }

        try {
            // 获取对象执行一次finalize方法
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
                jla.invokeFinalize(finalizee);

                // Clear stack slot containing this variable, to decrease
                // the chances of false retention with a conservative GC
                // 清空变量引用从而减小保守GC致使变量保留的可能性
                finalizee = null;
            }
        } catch (Throwable x) { }
        // 执行完毕会作一次状况防止重复执行
        super.clear();
    }

    /* Create a privileged secondary finalizer thread in the system thread
     * group for the given Runnable, and wait for it to complete.
     *
     * This method is used by runFinalization.
     *
     * It could have been implemented by offloading the work to the
     * regular finalizer thread and waiting for that thread to finish.
     * The advantage of creating a fresh thread, however, is that it insulates
     * invokers of that method from a stalled or deadlocked finalizer thread.
     */
    // 这里其实不用畏惧注释太多,它只是一个候选方法,新建一个线程直接调用包裹在Runnable的runFinalization方法,主要是提供给主动调用的上层方法调用的
    private static void forkSecondaryFinalizer(final Runnable proc) {
        AccessController.doPrivileged(
            new PrivilegedAction<>() {
                public Void run() {
                    ThreadGroup tg = Thread.currentThread().getThreadGroup();
                    for (ThreadGroup tgn = tg;
                         tgn != null;
                         tg = tgn, tgn = tg.getParent());
                    Thread sft = new Thread(tg, proc, "Secondary finalizer", 0, false);
                    sft.start();
                    try {
                        sft.join();
                    } catch (InterruptedException x) {
                        Thread.currentThread().interrupt();
                    }
                    return null;
                }});
    }

    /* Called by Runtime.runFinalization() */
    // 这个方法是给Runtime.runFinalization()委托调用的,其实就是主动取出queue的元素强制调用其finalize方法
    static void runFinalization() {
        if (VM.initLevel() == 0) {
            return;
        }
        forkSecondaryFinalizer(new Runnable() {
            private volatile boolean running;
            public void run() {
                // in case of recursive call to run()
                if (running)
                    return;
                final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
                running = true;
                for (Finalizer f; (f = (Finalizer)queue.poll()) != null;)
                    f.runFinalizer(jla);
            }
        });
    }
    
    // 真正的Finalizer线程
    private static class FinalizerThread extends Thread {
        private volatile boolean running;
        FinalizerThread(ThreadGroup g) {
            super(g, null, "Finalizer", 0, false);
        }
        public void run() {
            // in case of recursive call to run()
            if (running)
                return;

            // Finalizer thread starts before System.initializeSystemClass
            // is called.  Wait until JavaLangAccess is available
            while (VM.initLevel() == 0) {
                // delay until VM completes initialization
                try {
                    VM.awaitInitLevel(1);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
            final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
            running = true;
            // 注意这里是死循环
            for (;;) {
                try {
                    // 注意这里是调用`Reference#remove()`的永久阻塞版本,只有`Reference#enqueue()`被调用才会解除阻塞
                    // `Reference#remove()`解除阻塞说明元素已经完成入队,由ReferenceHandler线程完成
                    Finalizer f = (Finalizer)queue.remove();
                    // 实际上就是调用对象的finalize方法
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
        }
    }

    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        // 静态代码块中声明线程,优先级是最高优先级-2,守护线程,实际上这里优先级不必定会生效
        Thread finalizer = new FinalizerThread(tg);
        finalizer.setPriority(Thread.MAX_PRIORITY - 2);
        finalizer.setDaemon(true);
        finalizer.start();
    }
}

上面的注释已经很明显标注出来,这里小结一下内容。

  • FinalizerFinalReference的子类,而FinalReferenceReference的实现,因此它的工做原理和其余引用相似,对象的状态更变和由ReferenceHandler线程密切相关。
  • Finalizer内部维护了一个链表,每当JVM调用静态注册方法就会新建一个Finalizer实例加入到链表的头节点中,头节点元素为unfinalized,这里称此链表为unfinalized链表。
  • Finalizer线程由Finalizer静态代码块构建而且运行,它是守护线程,优先级是最高优先级-2,它的做用就是提取unfinalized链表的元素而且执行元素对象的finalize()方法,过程当中还会涉及到线程的阻塞、唤醒,以及unfinalized链表的重建等工做。

因为静态方法Finalizer#register(Object finalizee)是由JVM调用的,因此咱们必需要分析一些JVM的源码,参考的是OpenJDK主分支的代码,文件是instanceKlass.cpp

instanceOop InstanceKlass::register_finalizer(instanceOop i, TRAPS) {
  if (TraceFinalizerRegistration) {
    tty->print("Registered ");
    i->print_value_on(tty);
    tty->print_cr(" (" INTPTR_FORMAT ") as finalizable", p2i(i));
  }
  instanceHandle h_i(THREAD, i);
  // Pass the handle as argument, JavaCalls::call expects oop as jobjects
  JavaValue result(T_VOID);
  JavaCallArguments args(h_i);
  // 这里Universe::finalizer_register_method()获取到的就是Finalizer#register方法句柄
  methodHandle mh (THREAD, Universe::finalizer_register_method());
  JavaCalls::call(&result, mh, &args, CHECK_NULL);
  return h_i();
}

最后调用的是javaCalls.cpp

void JavaCalls::call(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS) {
  // Check if we need to wrap a potential OS exception handler around thread
  // This is used for e.g. Win32 structured exception handlers
  assert(THREAD->is_Java_thread(), "only JavaThreads can make JavaCalls");
  // Need to wrap each and every time, since there might be native code down the
  // stack that has installed its own exception handlers
  os::os_exception_wrapper(call_helper, result, method, args, THREAD);
}

简单来看就是把建立对象过程当中,若是有必要注册Finalizer(通常是覆盖了finalize()方法),则基于当前线程经过Finalizer#register(Object finalizee)把当前新建的实例注册到Finalizer自身维护的链表中(若是没理解错,所谓的F-Queue就是这个链表了),等待后台Finalizer线程轮询而且执行链表中对象的finalize()方法。

各种引用以及它们的使用场景

这里提到的各种引用目前就是四种:强引用(StrongReference)、软引用(SoftReference)、弱引用(WeakReference)和虚引用(PhantomReference)。其实还有特殊的引用类型FinalReference,它是包私有的,而且只有一个子类型Finalizer

StrongReference

StrongReference也就是强引用,它是使用最广泛的一种引用,java.lang.ref包下没有强引用对应的类型。一个比较明确的强引用定义就是:全部和GC Root之间存在引用链的对象都具有强引用。举个简单例子:形如Object o = new Object();在方法体中使用new关键字声明的对象通常就是强引用。若是一个对象具有强引用,垃圾回收器毫不会回收它。当内存空间不足,JVM宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会出现回收具备强引用的对象来解决内存不足的状况。固然,若是有共享的成员变量在方法退出以前置为null,至关于断绝成员变量和GC Root的引用链,在合适的时机是有利于GC后具有强引用的对象的回收,例如:

private Object shareValue = XXX;

public void methodA(){
    //do something
    shareValue = null;
}

后来有人过分信奉相似上面的这个实践,出现了一条比较诡异的编码实践:强引用使用完毕后都要置为null方便对象回收。可是实际上,这个实践并非在任何场景都是合理的。

SoftReference

SoftReference也就是软引用,它是用来描述一些"还有用可是非必须"的对象。对于软引用关联着的对象,在JVM应用即将发生内存溢出异常以前,将会把这些软引用关联的对象列进去回收对象范围之中进行第二次回收。若是此次回收以后仍是没有足够的内存,才会抛出内存溢出异常。简单来讲就是:

  • 若是内存空间足够,垃圾回收器就不会回收软引用关联着的对象。
  • 若是内存空间不足,垃圾回收器在将要抛出内存溢出异常以前会回收软引用关联着的对象。

举个简单的使用例子:

// VM参数:-Xmx4m -Xms4m
public class SoftReferenceMain {

    public static void main(String[] args) throws Exception {
        ReferenceQueue<SoftReferenceObject> queue = new ReferenceQueue<>();
        SoftReferenceObject object = new SoftReferenceObject();
        SoftReference<SoftReferenceObject> reference = new SoftReference<>(object, queue);
        object = null;
        System.gc();
        Thread.sleep(500);
        System.out.println(reference.get());
    }

    private static class SoftReferenceObject {

        int[] array = new int[120_000];

        @Override
        public String toString() {
            return "SoftReferenceObject";
        }
    }
}
// 运行后输出结果
null

上面的例子故意把JVM的启动的最大Heap内存和初始Heap内存设置为4MB,使用这个对象初始化一个比较大的整型数组而且关系到一个软引用对象中,GC以后,发现软引用关联的对象被回收了。

WeakReference

WeakReference也就是弱引用,弱引用和软引用相似,它是用来描述"非必须"的对象的,它的强度比软引用要更弱一些。被弱引用关联的对象只能生存到下一次垃圾收集发生以前,简言之就是:一旦发生GC一定回收被弱引用关联的对象,无论当前的内存是否足够。

举个例子:

public class WeakReferenceMain {

    public static void main(String[] args) throws Exception {
        ReferenceQueue<WeakReferenceObject> queue = new ReferenceQueue<>();
        WeakReferenceObject object = new WeakReferenceObject();
        System.out.println(object);
        WeakReference<WeakReferenceObject> reference = new WeakReference<>(object, queue);
        object = null;
        System.gc();
        Thread.sleep(500);
        System.out.println(reference.get());
    }

    private static class WeakReferenceObject {

        @Override
        public String toString() {
            return "WeakReferenceObject";
        }
    }
}
// 运行后输出结果
WeakReferenceObject
null

上面的例子中没有设定JVM的堆内存,所以不存在内存不足的状况,可见弱引用关联的对象在GC以后被回收了。弱引用适合用来作对内存敏感的缓存,很经常使用的WeakHashMap就是基于弱引用实现的。

PhantomReference

PhantomReference也就是虚引用,也叫幽灵引用或者幻影引用,它是全部引用类型中最弱的一种。一个对象是否关联到虚引用,彻底不会影响该对象的生命周期,也没法经过虚引用来获取一个对象的实例(PhantomReference覆盖了Reference#get()而且老是返回null)。为对象设置一个虚引用的惟一目的是:能在此对象被垃圾收集器回收的时候收到一个系统通知PhantomReference有两个比较经常使用的子类是java.lang.ref.Cleanerjdk.internal.ref.Cleaner,其中前者提供的功能是开发者用于在引用对象回收的时候触发一个动做(java.lang.ref.Cleaner$Cleanable),后者用于DirectByteBuffer对象回收的时候对于堆外内存的回收,能够翻看前面描述java.lang.ref.Reference#processPendingReferences()源码的时候,ReferenceHandler线程会对pending链表中的jdk.internal.ref.Cleaner类型引用对象调用其clean()方法。PhantomReference自己使用场景比较少,这里举一下java.lang.ref.Cleaner注释中的例子:

public class PhantomReferenceMain {

    public static void main(String[] args) throws Exception {
        try (CleaningExample o = new CleaningExample(11)){

        }
        CleaningExample o2 = new CleaningExample(22);
        System.gc();
        Thread.sleep(300);
    }

}

class CleaningExample implements AutoCloseable {

    private Cleaner cleaner = Cleaner.create();
    private final State state;
    private final Cleaner.Cleanable cleanable;

    public CleaningExample(int s) {
        state = new State(s);
        cleanable = cleaner.register(this, state);
    }

    class State implements Runnable {

        private final int s;

        public State(int s) {
            this.s = s;
        }

        @Override
        public void run() {
            System.out.println("State runnable in action.State value = " + s);
        }
    }

    @Override
    public void close() throws Exception {
        cleanable.clean();
    }
}

实际上,沙面的代码执行完毕只会输出"State runnable in action.State value = 11",并无输出"State runnable in action.State value = 22",这是由于没法预测强引用对象被回收的时机。java.lang.ref.Cleaner主要是用于预防实现了AutoCloseable接口的实例忘记调用close()方法在对象被垃圾收集器回收的时候(内存回收)作一个兜底的清理工做,在JDK9以后,java.lang.ref.Cleaner主要是为了替代已经标识为过时的Object#finalize()方法。

扩展阅读:能够注意阅读一下《Effective Java 3rd》的第8小节,摘抄部份内容以下:终结方法(Finalizer)是不可预知的,不少时候是危险的,并且通常状况下是没必要要的。...在Java 9中,终结方法已经被遗弃了,但它们仍被Java类库使用,相应用来替代终结方法的是清理方法(cleaner)。比起终结方法,清理方法相对安全点,但还是不能够预知的,运行慢的,并且通常状况下是没必要要的。

JDK9中有不少原来使用覆盖Object#finalize()方法的清理工做实现都替换为java.lang.ref.Cleaner,可是仍然不鼓励使用这种方式。

Reference和ReferenceQueue配合使用

前面基本介绍完了全部类型引用以及相关的源码,可是还没有提供例子说明ReferenceReferenceQueue是怎么配合使用的。举个例子:

public class ReferenceQueueMain {

    public static void main(String[] args) throws Exception {
        ReferenceQueue<WeakReferenceObject> queue = new ReferenceQueue<>();
        WeakReferenceObject object = new WeakReferenceObject();
        WeakReference<WeakReferenceObject> weakReference = new WeakReference<>(object, queue);
        System.out.println(weakReference);
        object = null;
        System.gc();
        Thread.sleep(500);
        while (true) {
            Reference<? extends WeakReferenceObject> reference = queue.poll();
            if (null == reference) {
                Thread.sleep(100);
            } else {
                System.out.println(reference);
                System.out.println(reference.get());
                break;
            }
        }
    }

    private static class WeakReferenceObject {

        @Override
        public String toString() {
            return "WeakReferenceObject";
        }
    }
}

运行后输出结果是:

java.lang.ref.WeakReference@6537cf78
java.lang.ref.WeakReference@6537cf78
null

可见轮询ReferenceQueue实例获得的弱引用实例和建立的是一致的,只是它持有的关联的对象已经被回收,获得null。上面的ReferenceQueue#poll()方法也能够替换为ReferenceQueue#remove(),这样子就不用写在死循环中,由于ReferenceQueue#remove()会阻塞到有元素能够出队。经过轮询绑定到Reference实例的ReferenceQueue实例,就能够得知Reference实例当前的状态而且判断它关联的咱们真正关注的对象是否被回收。

小结

  • Reference非强引用的其余三种引用的共同父类。
  • ReferenceQueue只存储了引用链表的头节点,提供了引用链表的操做,实际上,引用链表是Reference实例内部变量存储的。
  • ReferenceHandler守护线程线程由Reference的静态代码块建立和运行,做用是处理pending链表的引用元素使之状态变动,伴随着ReferenceQueue的相关操做。
  • Finalizer守护线程是由Finalizer类的静态代码块建立和运行的,做用是处理Finalizer类内部维护的F-Queue链表(链表元素入队操做由JVM实现)的元素调用关联对象的finalize()方法。
  • ReferenceHandler守护线程线和Finalizer守护线程共同协做才能使引用类型对象内存回收系统的工做可以正常进行。

四种引用类型的总结

引用类型 被垃圾收集器回收的时机 主要用途 生存周期
强引用 直到内存溢出也不会回收 广泛对象的状态 从建立到JVM实例终止运行
软引用 垃圾回收而且内存不足时 有用但非必须的对象缓存 从建立到垃圾回收而且内存不足时
弱引用 垃圾回收时 非必须的对象缓存 上一次垃圾回收结束到下一次垃圾回收开始
虚引用 - 关联的对象被垃圾收集器回收时候获得一个系统通知 -

参考资料:

  • JDK11部分源码。
  • 《深刻理解Java虚拟机-2nd》- 这本书算是国内书籍写得比较良心的一本了,不过有不少小的问题或者笔误之处,须要自行发现和修正。

我的博客

(过年比较懒,好久没发文 e-a-20190215 c-14-d)

相关文章
相关标签/搜索