[NIO和Netty] NIO和Netty系列(二): Java Reference详解

在分析DirectByteBuffer源码时发现使用到了java.lang.ref.Cleaner类,该类就是一个Reference,因此这里对Java中的Reference做个小结。

Reference系列的类定义在java.lang.ref包(openjdk11)下:
[NIO和Netty] NIO和Netty系列(二): Java Reference详解_第1张图片

Java中引用的分类

在Java中,有四种Reference类型,从强到弱,依次如下:

  • 强引用(Strong Reference):强引用就是指在程序代码之中普遍存在的,类似Object obj = new Object()这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象,甚至不惜抛出OOM异常;
  • 软引用(Soft Reference):软引用使用来描述一些还有用但是非必须的对象,对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收返回之中进行第二次回收,如果这次回收还没有足够的内存,才会跑出内存溢出。在 JDK 1.2 之后,提供了 SoftReference 类来实现软引用,软引用通常用于实现一些内存敏感型的cache;
  • 弱引用(Weak Reference):弱引用也是用来描述非必须对象的,但是它的强度比软引用更弱一些,被弱引用关联的喜爱那个只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象,在 JDK 1.2 之后,提供了 WeakReference 类来实现弱引用,WeakHashMap中的key就是弱引用;
  • 虚引用(Phantom Reference):虚引用也被成为幽灵引用或者幻影引用,它是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被垃圾回收器回收的时取得一个系统通知,用于做后续相关清理工作,在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用;

Reference && ReferenceQueue

Reference类有两个构造方法:

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

第一个构造方法传入一个该Reference实际指向的对象,这里为方便描述,我们称之为原对象,调用第二个构造方法并且queue传入null。原对象被保存在Referencereferent属性当中,当传入的queue为null时,Reference的queue属性值为ReferenceQueue.NULL,否则将传入的queue保存到Reference的queue属性中。

ReferenceQueue

java.lang.ref.ReferenceQueue用于保存一个Reference队列,内部有一个head属性:

private volatile Reference<? extends T> head;

head属性指向Reference队列的头元素,并且提供了入队和出队的方法。

Reference属性

java.lang.ref.Reference是所有引用类型的父类,Reference类定义了如下几个属性:

  • referent
    private T referent;
    
    Reference所指向的实际对象,即原对象;
  • queue
    volatile ReferenceQueue<? super T> queue;
    
    当一个Reference的原对象被垃圾回收时,会将该Reference对象本身插入到queue的头部;
  • next
    volatile Reference next
    
    用于指向queue队列里的下一个元素,注意queue本身只保存队列的头元素(head成员),队列里的其他元素依靠Referencenext属性来连接;
  • discovered
    private transient Reference<T> discovered;
    
    Reference里的另外一个链表,当GC回收原对象时,JVM会将所有原对象对应的Reference对象用discovered属性连接成一个链表(jdk8中的pending属性指向其头部),然后遍历该链表,将链表里的元素依次取出放入到queue队列中或者做一些清理资源的工作,每将一个Reference对象加入到queue队列,就将该对象的queue属性改为ENQUEUED,标志该对象已经加入到queue队列中,ENQUEUED定义如下
    private static class Null extends ReferenceQueue<Object> {
        boolean enqueue(Reference<?> r) {
            return false;
        }
    }
    
    static final ReferenceQueue<Object> NULL = new Null();
    static final ReferenceQueue<Object> ENQUEUED = new Null();
    
    当queue为NULL(new Null()),代表该Reference对象不需要加入到queue队列当中;

Reference状态

当向Reference对象传入的queue为null和不为null时,Reference的状态变化有所不同,Reference在整个生命周期中存在如下几种状态:

  • Active:Reference对象刚创建时处于Active状态,直到GC检测到原对象的的可达性变化到合适的状态前,当GC检测到原对象的的可达性变化到合适的状态时会通知Reference对象,更改其状态为pending(构造Reference对象时传入的queue不为null)或者inactive(构造Reference对象时传入的queue为null);
  • Pendingreferent指向的对象被GC回收或即将被GC回收时对应的Reference对象处于Pending状态,此时referent属性值为null,discovered属性指向下一个即将入队到queue的Reference元素(pending列表的下一个元素);
  • Inactive:既不处于Active状态也不处于Pending状态,referent属性值为null;
  • Registered:调用Reference(T referent, ReferenceQueue queue)构造方法传入了queue,并且Reference还未入queue期间Reference对象处于Registered状态;
  • Enqueued:Reference对象已经加入到queue队列中,并且尚未从queue移除,此时queue的值为 ReferenceQueue.ENQUEUE,标记Reference对象已经入队,防止重复入队,next指向队列的下一个元素或者指向该Reference对象本身(Reference对象是队列中最后一个元素的情况下);
  • Dequeued:Reference对象已经从queue队列中移除,此时queue值为ReferenceQueue.NULLnext指向Reference对象本身;
  • Unregistered:调用Reference(T referent)构造方法未传入queue,此时queue的值始终为ReferenceQueue.NULL

按照构造Reference对象时传入的queue是否为null,Reference对象状态变化分两组情况。

传入的queue为null时状态变化

传入queue为null时,Reference对象始终是unregistered状态,当元对象被回收或即将被回收时,会进入到pending状态,GC会将所有原对象被回收或即将被回收的Reference对象合并到pending Reference列表当中去,处于pending Reference列表中的Reference对象都处于pending状态,然后试图将pending列表入队(事实上因为并未传入queue,所以入队失败),入队结束进入inactive状态,如下图所示:
[NIO和Netty] NIO和Netty系列(二): Java Reference详解_第2张图片

传入的queue不为null时

这种情况比较复杂,我仔细读了下java doc依然没有理解部分状态的转换,这里先直接将java doc贴出来,待日后补充:

     * 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.

Reference示例

如下示例代码所示:

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class ReferenceTest {

    private static ReferenceQueue<Object> queue = new ReferenceQueue<>();

    public static void main(String[] args) throws Exception {

        List<Reference<Object>> references = new ArrayList<>();

        // 创建ReferenceQueue队列
        ReferenceQueue<Object> queue = new ReferenceQueue<>();

        // 创建软引用,只有在GC发现内存不足时才会回收softRef所指向的对象
        Object obj = new Object();
        SoftReference<Object> softRef = new SoftReference<>(obj, queue);
        // 创建7个弱引用对象
        Object obj1 = new Object();
        Object obj2 = new Object();
        Object obj3 = new Object();
        Object obj4 = new Object();
        WeakReference<Object> weakRef1 = new WeakReference<>(obj1, queue);
        WeakReference<Object> weakRef2 = new WeakReference<>(obj2, queue);
        WeakReference<Object> weakRef3 = new WeakReference<>(obj3, queue);
        WeakReference<Object> weakRef4 = new WeakReference<>(obj4, queue);

        // 将Reference对象本身用强引用保存起来
        references.add(softRef);
        references.add(weakRef1);
        references.add(weakRef2);
        references.add(weakRef3);
        references.add(weakRef4);

        // 将Object对象引用置为null,使得没有强引用指向Object对象
        obj1 = null;
        obj2 = null;
        obj3 = null;
        obj4 = null;

        // 手动触发GC,obj1、obj2、obj3、obj4四个对象将被回收
        System.gc();

        TimeUnit.SECONDS.sleep(2);

        Reference ref;
        while ((ref = queue.poll()) != null) {
            System.out.println("reference polled from queue: " + ref);
        }
        TimeUnit.SECONDS.sleep(1000);
    }
}

运行结果如下:

reference polled from queue: java.lang.ref.WeakReference@52a86356
reference polled from queue: java.lang.ref.WeakReference@5ce81285
reference polled from queue: java.lang.ref.WeakReference@78c03f1f
reference polled from queue: java.lang.ref.WeakReference@5ec0a365

可见弱引用已经入队,而软引用不再队列中,因为此时GC认为内存充足,不会回收softRef的原对象。我们运行程序,并在ReferenceprocessPendingReferences方法(稍后我们将分析该方法源码)里打断点,切换到ReferenceHandler线程,观察pending列表:
[NIO和Netty] NIO和Netty系列(二): Java Reference详解_第3张图片
[NIO和Netty] NIO和Netty系列(二): Java Reference详解_第4张图片
此时GC已经将obj1、obj2、obj3、obj4四个对象回收,之后JVM将这四个对象的Reference对象通过discovered属性连接起来构成pending链表赋值给上图中的pendingList,遍历pendingList将链表中的Reference对象入队,入队结束,pendingList值为null,切换到main线程,观察queue队列:
[NIO和Netty] NIO和Netty系列(二): Java Reference详解_第5张图片
queue队列此时已经指向weakRef的对头元素,队列元素通过next成员连接,各个weakRef对象里持有的queue是ReferenceQueue里的ENQUEUED静态成员,定义如下:

private static class Null extends ReferenceQueue<Object> {
     boolean enqueue(Reference<?> r) {
         return false;
     }
}

static final ReferenceQueue<Object> NULL = new Null();
static final ReferenceQueue<Object> ENQUEUED = new Null();

ENQUEUED用于标识Reference对象已经入队,NULL用于标识Reference对象不需要入队(调用的是不带queue参数的构造方法)。

示例代码图解

初始状态:刚创建软引用和弱引用并且尚未发生GC时,即执行完如下代码

// 创建ReferenceQueue队列
ReferenceQueue<Object> queue = new ReferenceQueue<>();

// 创建软引用,只有在GC发现内存不足时才会回收softRef所指向的对象
Object obj = new Object();
SoftReference<Object> softRef = new SoftReference<>(obj, queue);
// 创建7个弱引用对象
Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = new Object();
Object obj4 = new Object();
WeakReference<Object> weakRef1 = new WeakReference<>(obj1, queue);
WeakReference<Object> weakRef2 = new WeakReference<>(obj2, queue);
WeakReference<Object> weakRef3 = new WeakReference<>(obj3, queue);
WeakReference<Object> weakRef4 = new WeakReference<>(obj4, queue);

并且尚未发生GC时,各对象的引用关系如下图,discovered和next属性都为null,queue属性指向同一个queue
[NIO和Netty] NIO和Netty系列(二): Java Reference详解_第6张图片
手动触发GC:此时弱引用指向的对象都会被回收,由于JVM内存充足,软引用指向的对象不会被回收,JVM会将弱引用对象本身通过discovered属性连接成一个pendingList列表,此时各对象的引用关系如下

// 将Object对象引用置为null,使得没有强引用指向Object对象
obj1 = null;
obj2 = null;
obj3 = null;
obj4 = null;

// 手动触发GC,obj1、obj2、obj3、obj4四个对象将被回收
System.gc();

[NIO和Netty] NIO和Netty系列(二): Java Reference详解_第7张图片
链表入队:ReferenceHandler线程会不断获取pendingList链表,然后将Reference对象加入到queue队列当中,入队后Reference对象中的属性queue指向ENQUEUED对象,标识已经入队,queue中队列元素通过next属性连接
[NIO和Netty] NIO和Netty系列(二): Java Reference详解_第8张图片

源码分析

Reference中的属性前面已经介绍,Reference类中有一个静态的内部线程类:

/* High-priority thread to enqueue pending References
 */
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 {
        // 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);
    }

    public void run() {
        while (true) {
           // run方法里不断调用processPendingReferences方法获取pendingList然后入队
           processPendingReferences();
        }
    }
}

ReferenceHandler线程在静态代码块中被创建和启动:

static {
    ThreadGroup tg = Thread.currentThread().getThreadGroup();
     for (ThreadGroup tgn = tg;
          tgn != null;
          tg = tgn, tgn = tg.getParent());
     // 创建ReferenceHandler线程
     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();

     // provide access in SharedSecrets
     SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
         @Override
         public boolean waitForReferenceProcessing()
             throws InterruptedException
         {
             return Reference.waitForReferenceProcessing();
         }

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

如下所示为processPendingReferences方法的实现:

/*
 * Atomically get and clear (set to null) the VM's pending-Reference list.
 * native方法获取pendingList列表
 */
private static native Reference<Object> getAndClearReferencePendingList();

/*
 * Wait until the VM's pending-Reference list may be non-null.
 * native方法等待获取pendingList列表
 */
private static native void waitForReferencePendingList();

private static final Object processPendingLock = new Object();
private static boolean processPendingActive = false;
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().
    // 等待获取pendingList列表
    waitForReferencePendingList();
    Reference<Object> pendingList;
    synchronized (processPendingLock) {
    	 // 获取pendingList列表
         pendingList = getAndClearReferencePendingList();
         processPendingActive = true;
    }
    while (pendingList != null) {
        Reference<Object> ref = pendingList;
        // 获取下一个原对象被GC或即将被GC的对应的Reference对象
        pendingList = ref.discovered;
        ref.discovered = null;

         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 {
             ReferenceQueue<? super Object> q = ref.queue;
             // 将Reference对象入队
             if (q != ReferenceQueue.NULL) q.enqueue(ref);
         }
    }
    // Notify any waiters of completion of current round.
    synchronized (processPendingLock) {
        processPendingActive = false;
        processPendingLock.notifyAll();
    }
}

ReferenceQueue的入队代码:

private static class Null extends ReferenceQueue<Object> {
    boolean enqueue(Reference<?> r) {
        return false;
    }
}

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;
private long queueLength = 0;

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<?> queue = r.queue;
        // 判断Reference对象是否不需要入队或者已经入队
        if ((queue == NULL) || (queue == ENQUEUED)) {
            return false;
        }
        assert queue == this;
        // Self-loop end, so if a FinalReference it remains inactive.
        // 将Reference对象插入queue的头部,并修改queue的head指向插入的Reference对象
        r.next = (head == null) ? r : head;
        head = r;
        queueLength++;
         // Update r.queue *after* adding to list, to avoid race
         // with concurrent enqueued checks and fast-path poll().
         // Volatiles ensure ordering.
         // 将Reference对象的queue属性设置为ENQUEUED,标识Reference对象已经入队
         r.queue = ENQUEUED;
         if (r instanceof FinalReference) {
             VM.addFinalRefCount(1);
         }
         // 通知其他线程有Reference对象入队了,可以从队列取下元素进行处理了
         lock.notifyAll();
         return true;
    }
}

以上分析了Reference和ReferenceQueue的实现,由于我在NIO时遇到的是Cleaner这个Reference,因此这里接下来会对Cleaner作分析。

Cleaner && PhantomReference

这里所说的Cleaner是jdk.internal.ref.Cleaner,并不是java.lang.ref.Cleaner,事实上java.lang.ref.Cleaner并不是Reference,在DirectByteBuff中使用的也是jdk.internal.ref.Cleaner,因此这里我们分析的是jdk.internal.ref.Cleaner

CleanerPhantomReference的子类,PhantomReference的定义非常简单

public class PhantomReference<T> extends Reference<T> {

    public T get() {
        return null;
    }

    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

PhantomReferenceget方法总是返回null,也就是我们无法通过get方法获取到原对象,正如前所述:虚引用主要被用来 跟踪对象被垃圾回收的状态,通过查看引用队列中是否包含对象所对应的虚引用来判断它是否即将被GC回收,从而采取行动。它并不被期待用来取得目标对象的引用,而目标对象被回收前,它的引用会被放入一个 ReferenceQueue 对象中,从而达到跟踪对象垃圾回收的作用。

Cleaner继承了PhantomReferenceCleaner维护了一个全局唯一的双向链表,用静态成员first指向该链表:

private static Cleaner first = null;

整个JVM运行过程中产生的所有Cleaner都会添加到这个链表当中,Cleaner通过next寻找到下一个Cleaner,通过prev寻找到上一个Cleaner

private Cleaner next = null, prev = null;

通过如下方法创建Cleaner

// dummyQueue在Cleaner没有起到任何作用,ReferenceHandler不会将Cleaner对象加入到dummyQueue当中,
// 查看ReferenceHandler线程的run方法可以发现当遇到的Reference对象的实际类型是Cleaner时,
// 只是简单的调用其clean方法,定义dummyQueue的原因仅仅因为其父类PhantomReference的
// 构造方法中需要传入一个queue
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
private final Runnable thunk;

private Cleaner(Object referent, Runnable thunk) {
    super(referent, dummyQueue);
    this.thunk = thunk;
}

// 将Cleaner对象添加到双向链表当中
private static synchronized Cleaner add(Cleaner cl) {
     if (first != null) {
         cl.next = first;
         first.prev = cl;
     }
     first = cl;
     return cl;
}

// thunk对象里可以自定义资源清理行为
public static Cleaner create(Object ob, Runnable thunk) {
    if (thunk == null)
        return null;
    return add(new Cleaner(ob, thunk));
}

Cleaner的清理资源方法:

private static synchronized boolean remove(Cleaner cl) {

    // If already removed, do nothing
    if (cl.next == cl)
         return false;

    // Update list
    // cl的链表的第一个元素的情况下需要更改first的指向
    if (first == cl) {
         if (cl.next != null)
             first = cl.next;
         else
             first = cl.prev;
    }
    // cl后一个元素的prev指向cl中prev指向的元素,cl前一个元素的next指向cl的后一个元素
    if (cl.next != null)
        cl.next.prev = cl.prev;
    if (cl.prev != null)
        cl.prev.next = cl.next;

    // Indicate removal by pointing the cleaner to itself
    // cl的next和prev都指向自己时表示cl已经从链表中移除了
    cl.next = cl;
    cl.prev = cl;
    return true;
}

// 该方法
public void clean() {
	 // 将cleaner从链表中取下,防止重复执行清除操作
     if (!remove(this))
         return;
     try {
     	 // thunk的run方法里执行自定义的资源清理操作
         thunk.run();
     } catch (final Throwable x) {
         AccessController.doPrivileged(new PrivilegedAction<>() {
                 public Void run() {
                     if (System.err != null)
                         new Error("Cleaner terminated abnormally", x)
                                .printStackTrace();
                     System.exit(1);
                     return null;
                 }});
     }
}

JDK中是如何使用Cleaner的:
回到Reference类中定义的ReferenceHandler线程,其run方法如下:

private static class ReferenceHandler extends Thread {
    public void run() {
        while (true) {
            processPendingReferences();
        }
    }
}

processPendingReferences方法如下:

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().
        waitForReferencePendingList();
        Reference<Object> pendingList;
        synchronized (processPendingLock) {
            pendingList = getAndClearReferencePendingList();
            processPendingActive = true;
        }
        while (pendingList != null) {
            Reference<Object> ref = pendingList;
            pendingList = ref.discovered;
            ref.discovered = null;
			// 判断Reference对象的实际类型是否为Cleaner
            if (ref instanceof Cleaner) {
                // 是的话直接执行clean方法来清理资源
                ((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 {
                // 否则将Reference对象加入到队列中
                ReferenceQueue<? super Object> q = ref.queue;
                if (q != ReferenceQueue.NULL) q.enqueue(ref);
            }
        }
        // Notify any waiters of completion of current round.
        synchronized (processPendingLock) {
            processPendingActive = false;
            processPendingLock.notifyAll();
        }
}

即JVM收集到pendingList链表,如果链表元素是Cleaner则直接执行Cleaner的clean方法,否则将链表元素加入到队列当中,在Finalizer我们将分析jdk是如何处理该加入到队列当中的元素的。

Finalizer && FinalReference

之前我们分析到将Reference对象入队后就结束了,那么入队的Reference对象在何时出队呢?这里我们结合Finializer来说明queue中元素何时出队,出队后又进行怎样的处理。

什么情况下回会用到Finalizer类?

我们知道Object类中定义了一个空实现的finalize方法(从jdk9开始不推荐使用finalize方法,因为finalize方法可能导致对象被延迟回收),因为所有Java类都直接或间接继承Object类,因此所有类都继承了finalize方法,只不过默认情况下都是空实现,假设我们定义的一个类实现了finalize方法(非空实现),此时创建该类对象时就会使用到Finalizer,在该对象被GC标记为即将被回收时,Finalizer会去调用对象的finalize方法,进行资源的清理工作。

源码分析

我们还是结合源码分析JDK是如何使用FinalizerFinalizer继承FinalReferenceFinalReference源码如下:

class FinalReference<T> extends Reference<T> {

    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }

    @Override
    public boolean enqueue() {
        throw new InternalError("should never reach here");
    }
}

实现非常简单,enqueue方法直接抛出异常,根据Reference里对enqueue的说明:

/**
 * Clears this reference object and adds it to the queue with which
 * it is registered, if any.
 *
 * 

This method is invoked only by Java code; when the garbage collector * enqueues references it does so directly, without invoking this method. * * @return true if this reference object was successfully * enqueued; false if it was already enqueued or if * it was not registered with a queue when it was created */

enqueue方法是由Java代码来调用的(并不是由JVM来调用),所有继承自FinalReference的类都不能手动将Reference对象入队。

再看Finalizer,类中定义了几个重要属性:


private static ReferenceQueue<Object> queue = new ReferenceQueue<>();

/** 
 *  Head of doubly linked list of Finalizers awaiting finalization. 
 *  Finalizer的一个链表,unfinalized指向链表头元素,链表当中所有Finalizer对象
 *  所关联的那个原对象的finalize方法都尚未调用,已经调用finalize方法的原对象对
 *  应Finalizer对象将会从unfinalized链表移除
 */
private static Finalizer unfinalized = null;

/**
 * next指向链表中当前元素的下一个元素,prev指向链表当前元素的前一个元素
 */
private Finalizer next, prev;

继续看源码之前我们先大体了解下Finalizer的运作流程:

  • 当一个类实现了非空的finalize方法时,JVM会将该对象(原对象)与一个Finalizer对象关联起来;
  • JDK会将所有Finalizer对象连接成一个双向链表(防止Finalizer对象本身被GC回收),当原对象被GC标记为即将被回收时,将Finalizer对象加入到queue队列;
  • Finalizer在加载时在静态代码块中启动了一个FinalizerThread线程,该线程不断的从queue队列获取Finalizer对象,然后通过Finalizer对象调用对应原对象的finalize方法;
  • finalize方法执行完毕原对象才可能真正的被GC回收;

从以上大致流程可以看出一个实现了非空finalize方法的对象至少需要经过两次GC才能被回收掉(第一次GC标记即将被回收,finalize方法执行完并进行下一次GC时原对象才可能被回收)。

有了这个大致流程后我们就可以来分析源码了
先看Finalizer的构造方法:

/**
 * 私有构造方法,只能在Finalizer内部调用,finalizee是Finalizer对象关联的原对象
 */
private Finalizer(Object finalizee) {
    // 将Finalizer对象注册到queue队列当中
    super(finalizee, queue);
    // push onto unfinalized
    synchronized (lock) {
        // 当前链表不为空,将当前Finalizer对象插入到链表头部
        if (unfinalized != null) {
            this.next = unfinalized;
            unfinalized.prev = this;
        }
        // unfinalized指向链表头部元素
        unfinalized = this;
    }
}

再看register方法

/**
 * Invoked by VM
 * register由JVM调用,当JVM发现一个类实现了非空的finalize方法,JVM就调用该方法,
 * 将类对象作为finalizee参数传入,register调用Finalizer构造方法构造Finalizer
 * 对象并将其插入到unfinalized链表头部
 */
static void register(Object finalizee) {
    new Finalizer(finalizee);
}

FinalizerThread线程源码:

	// FinalizerThread线程
    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 {
                	// 从queue队列获取Finalizer对象,调用runFinalizer方法
                    Finalizer f = (Finalizer)queue.remove();
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
        }
    }

	/**
	 * 静态代码块中启动FinalizerThread线程,即Finalizer类被加载时就会启动该线程
	 */
    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread finalizer = new FinalizerThread(tg);
        finalizer.setPriority(Thread.MAX_PRIORITY - 2);
        finalizer.setDaemon(true);
        finalizer.start();
    }

    private void runFinalizer(JavaLangAccess jla) {
    	// 从unfinalized移除Finalizer对象
        synchronized (lock) {
            if (this.next == this)      // already finalized
                return;
            // unlink from unfinalized
            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 {
        	// 获取原对象
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
           		// 调用原对象的finalize方法
                jla.invokeFinalize(finalizee);

                // Clear stack slot containing this variable, to decrease
                // the chances of false retention with a conservative GC
                // 使finalizee不再指向原对象
                finalizee = null;
            }
        } catch (Throwable x) { }
        // 将referent置为null,使得原对象再没有强引用指向他
        super.clear();
    }

代码jla.invokeFinalize(finalizee);最终执行的代码如下,这里是真正执行原对象的finalize方法

// 定义在java.lang.System#setJavaLangAccess方法中
@SuppressWarnings("deprecation")
public void invokeFinalize(Object o) throws Throwable {
    o.finalize();
}

Reference相关内容到此结束…

参考

Java Reference详解
Reference、ReferenceQueue 详解
JVM源码分析之FinalReference完全解读

你可能感兴趣的:(java,jvm)