ReferenceQueue、Reference详解

本文深入探讨Java中Reference和ReferenceQueue的工作原理,包括其状态变迁、内部结构及ReferenceHandler线程的作用。通过实例演示Reference从活跃到无效的全过程。
转载自:http://www.jianshu.com/p/f86d3a43eec5
              http://www.cnblogs.com/jabnih/p/6580665.html

ReferenceQueue

       引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中,ReferenceQueue实现了入队(enqueue)和出队(poll),还有remove操作,内部元素head就是泛型的Reference:
    private volatile Reference<? extends T> head = null;
       ReferenceQueue的实现是由Reference自身的链表结构(单向循环链表)所实现的。所以说ReferenceQueue名义上是一个队列,但实际内部并非有实际的存储结构,它的存储是依赖于内部节点之间的关系来表达。来看下它的入队方法:
    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;
            if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            assert queue == this;
            r.queue = ENQUEUED;
            r.next = (head == null) ? r : head;
            head = r;
            queueLength++;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(1);
            }
            lock.notifyAll();
            return true;
        }
    }
       从enqueue()代码可以看出来,queue仅存储当前的head节点,而后面的节点由每个reference节点通过自己的next来保持的。而且queue是一个后进先出的队列。当新的节点进入时成为了head,然后出队时也是先出的head。

Reference

         java.lang.ref.Reference是SoftReference、WeakReference、PhantomReference的基类。Reference对象是和垃圾回收密切配合实现,Reference的直接子类都是由JVM定制化处理的,因此在代码中直接继承于Reference类型没有任何作用。但可以继承JVM定制的Reference的子类。例如:Cleaner就继承了PhantomReference。

构造函数
       Reference内部提供2个构造函数,一个带queue,一个不带queue。
    Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }
       其中queue的意义在于可以在外部对这个queue进行监控。如果有对象即将被回收,那么相应的reference对象就会被放到这个queue里,我们就拿到reference再作一些事情。而如果不带的话,就只有不断地轮询reference对象,通过判断里面的get是否返回null,来判断是否被回收。phantomReference对象不能这样作,其get始终返回null,因此它只有带queue的构造函数。queue的默认值是ReferenceQueue.NULL。
       以上这两种构造方法均有相应的使用场景,取决于实际的应用。如WeakHashMap中就选择去查询queue的数据,来判定是否有对象将被回收。而ThreadLocalMap则采用判断get()是否为null来作处理。
       如果在创建一个引用对象时,指定了ReferenceQueue,那么当引用对象指向的对象达到合适的状态(根据引用类型不同而不同)时,GC会把引用对象本身添加到这个队列中,方便我们处理它,因为“引用对象指向的对象GC会自动清理,但是引用对象本身也是对象(是对象就占用一定资源),所以需要我们自己清理。”

Reference内部主要的成员
    private T referent;         /* Treated specially by GC */
    volatile ReferenceQueue<? super T> queue;

    /* When active:   NULL
     *     pending:   this
     *    Enqueued:   next reference in queue (or this if last)
     *    Inactive:   this
     */
    @SuppressWarnings("rawtypes")
    Reference next;

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

    /* List of References waiting to be enqueued.  The collector adds
     * References to this list, while the Reference-handler thread removes
     * them.  This list is protected by the above lock object. The
     * list uses the discovered field to link its elements.
     */
    private static Reference<Object> pending = null;
         referent表示其引用的对象,即在构造的时候需要被包装在其中的对象。
         queue是对象即将被回收时所要通知的队列。当对象即将被回收时,整个reference对象,而不仅仅是被回收的对象,会被放到queue里面,然后外部程序即可通过监控这个queue拿到相应的数据了。
         next即当前引用节点所存储的下一个即将被处理的节点。但next仅在放到queue中才会有意义,因为只有在enqueue的时候,会将next设置为下一个要处理的Reference对象。为了描述相应的状态值,在放到队列当中后,其queue就不会再引用这个队列了。而是引用一个特殊的ENQUEUED(内部定义的一个空队列)。因为已经放到队列当中,并且不会再次放到队列当中。
        discovered表示要处理的对象的下一个对象。即可以理解要处理的对象也是一个链表,通过discovered进行排队,这边只需要不停地拿到pending,然后再通过discovered不断地拿到下一个对象赋值给pending即可,直到取到了最有一个。它是被JVM使用的。
        pending是等待被入队的引用列表。JVM收集器会添加引用到这个列表,直到Reference-handler线程移除了它们。这个列表使用discovered字段来连接它下一个元素(即pending的下一个元素就是discovered对象。r = pending; pending = r.discovered)。

Reference状态值
       每个引用对象都有相应的状态描述,即描述自己以及其包装的对象当前处于一个什么样的状态,以方便进行查询,定位或处理。Reference有4种状态。四种状态用Reference的成员变量queue与next来标示。
       Active:活动状态,新创建的引用对象都是这个状态,即相应的对象为强引用状态。在这个状态下next == null,queue为构造Reference对象时传入的ReferenceQueue对象,默认是ReferenceQueue.NULL。在GC检测到引用对象的可达性发生变化之后,它的状态将变化为Pending或Inactive(如果引用对象在构造时指定了ReferenceQueue,那么转移到Pending;如果没指定,转移到Inactive)。
       Pending:准备放入queue当中的引用对象,在这个状态的对象将挨个地排队放到queue当中。在这个时间窗口期,相应的对象为Pending状态,都在pending列表中。此状态的下,next == this(由JVM设置),queue为定义时所引用的queue。这里需要注意的是,queue如果指定了,那么引用对象能转移到Pending,如果没指定,直接转移到Inactive,因为此时next == this,queue == ReferenceQueue.NULL。
       Enqueued:引用对象已经放到queue当中了。准备由外部线程来询循queue获取相应的数据。调用ReferenceQueue.enqueued()后的reference就会处于这个状态中。此状态中next 为该queue中的下一个引用,如果是该队列中的最后一个,那么为this,queue = ReferenceQueue.ENQUEUED。当reference实例从它的ReferenceQueue移除后,它将成为Inactive。没有注册queue的实例不会进入这个状态。
       Inactive:即此对象已经由外部从queue中获取到,并且已经处理掉了,此时next == this,queue == ReferenceQueue.NULL。即意味着此引用对象可以被回收,并且对内部封装的对象也可以被回收掉了。但实际的回收运行取决于clear()方法是否被调用(clear()方法会设置this.referent = null)。

Reference类加载后,就会启动Reference Handler线程
    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        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();
    }
       从源码可以看出,这个线程在Reference类的static构造块中启动,并且被设置为最高优先级MAX_PRIORITY和daemon状态,并随即启动该线程。
    private static class ReferenceHandler extends Thread {
        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }

        public void run() {
            for (;;) {
                Reference<Object> r;
                synchronized (lock) {
                    if (pending != null) {
                        r = pending;
                        pending = r.discovered;
                        r.discovered = null;
                    } else {
                        try {
                            try {
                                lock.wait();
                            } catch (OutOfMemoryError x) { }
                        } catch (InterruptedException x) { }
                        continue;
                    }
                }

                // Fast path for cleaners
                if (r instanceof Cleaner) {
                    ((Cleaner)r).clean();
                    continue;
                }

                ReferenceQueue<Object> q = r.queue;
                if (q != ReferenceQueue.NULL) q.enqueue(r);
            }
        }
    }
       此线程要做的事情就是不断的检查pending是否为null,如果pending不为null,则将pending进行enqueue,否则线程进入wait状态。
       当Reference内部的referent对象的可达状态改变时,JVM会将该Reference对象放入pending链表,注意这时JVM会设置referent对象的next = this。并且这里enqueue的队列是在初始化Reference对象时传进来的queue,如果传入了null(实际使用的是ReferenceQueue.NULL),则ReferenceHandler则不进行enqueue操作,这时referent对象的状态已经是Inactive了。只有非RefernceQueue.NULL的queue才会将Reference进行enqueue。
          在enqueue()中,当reference实例的queue!=null && queue != ENQUEUED时;设置queue为ENQUEUED,next为下一个要处理的reference对象,或者若为最后一个则next==this。这时referent对象是Enqueue。
           当调用queue的remove或者poll方法时,就会将要处理的reference实例的queue设置为ReferenceQueue.NULL,next = this,此时reference实例就进入了Inactive状态,等待JVM回收。


具体执行例子:
// 创建一个引用队列
ReferenceQueue queue = new ReferenceQueue();

// 创建虚引用,此时状态为Active,并且Reference.pending为空,当前Reference.queue = 上面创建的queue,并且next=null
WeakReference reference = new WeakReference(new Object(), queue);
System.out.println(reference);
// 当GC执行后,由于是虚引用,所以回收该object对象,并且置于pending上,此时reference的状态为PENDING
System.gc();

/* ReferenceHandler从pending中取下该元素,并且将该元素放入到queue中,此时Reference状态为ENQUEUED,Reference.queue = ReferenceENQUEUED */

/* 当从queue里面取出该元素,则变为INACTIVE,Reference.queue = Reference.NULL */
Reference reference1 = queue.remove();
System.out.println(reference1);



<think>我们参考了提供的引用资料,并整合了关于Java四种引用的信息。四种引用分别是:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。下面将逐一详细解释。 ### 1. 强引用(Strong Reference) 强引用是最常见的引用类型,我们平时使用的普通对象引用就是强引用。只要强引用存在,垃圾回收器就永远不会回收被引用的对象。 ```java Object obj = new Object(); // 这就是一个强引用 ``` - **特点**:强引用可以直接访问目标对象,且只要强引用存在,对象就不会被回收。 - **回收时机**:当对象没有任何强引用指向时,才会被垃圾回收器回收。 - **使用场景**:一般对象都是通过强引用来使用。 ### 2. 软引用(Soft Reference) 软引用用来描述一些还有用但非必需的对象。在系统将要发生内存溢出异常之前,会把这些对象列进回收范围进行回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。 ```java SoftReference<Object> softRef = new SoftReference<>(new Object()); ``` - **特点**:当内存不足时,垃圾回收器会回收软引用指向的对象。 - **回收时机**:在内存不足时,由垃圾回收器决定回收。 - **使用场景**:适用于实现内存敏感的缓存,如图片缓存、网页缓存等。软引用可以和一个引用队列(`ReferenceQueue`)联合使用,如果软引用所引用的对象被回收,这个软引用就会被加入引用队列。 ### 3. 弱引用(Weak Reference) 弱引用也是用来描述非必需对象的,但它的强度比软引用更弱。被弱引用关联的对象只能生存到下一次垃圾回收发生之前。 ```java WeakReference<Object> weakRef = new WeakReference<>(new Object()); ``` - **特点**:无论内存是否充足,只要发生垃圾回收,弱引用指向的对象就会被回收。 - **回收时机**:在垃圾回收时,一旦发现弱引用,就会回收其对象。 - **使用场景**:适用于弱键缓存(如`WeakHashMap`),防止内存泄漏。弱引用也可以和引用队列联合使用。 ### 4. 虚引用(Phantom Reference) 虚引用也称为“幽灵引用”或“幻象引用”,它是最弱的一种引用关系。虚引用完全不会影响对象的生命周期,也无法通过虚引用来取得一个对象实例。 ```java ReferenceQueue<Object> queue = new ReferenceQueue<>(); PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue); ``` - **特点**:虚引用无法获取对象实例,其存在意义在于跟踪对象被垃圾回收的状态。 - **回收时机**:虚引用指向的对象在垃圾回收时会被回收,但虚引用本身不会自动清除,需要配合引用队列使用。 - **使用场景**:主要用于跟踪对象被垃圾回收的活动,例如在对象被回收时收到一个系统通知(通过检查引用队列)。在Java NIO中,堆外内存(`DirectByteBuffer`)的清理就利用了虚引用机制。 ### 引用队列(ReferenceQueue) 软引用、弱引用和虚引用都可以与引用队列一起使用。当引用所指向的对象被垃圾回收后,引用本身会被加入到引用队列中。这样,程序可以通过判断引用队列中是否有引用来了解被引用对象是否被回收。 --- §§相关问题§§ 1. 为什么软引用适合用于缓存场景? 2. 弱引用在`WeakHashMap`中是如何工作的? 3. 虚引用与引用队列如何配合使用来完成堆外内存的清理? 4. 在开发中如何选择合适的引用类型? </think>### Java四种引用详解 在Java中,对象引用分为四种类型,帮助开发者灵活管理内存和对象生命周期。以下根据引用资料[^1][^2][^3][^4][^5]详细解析: --- #### 1. **强引用(Strong Reference)** - **定义**:最常见的引用类型,通过`new`创建的对象默认是强引用。 - **特性**: - 只要强引用存在,对象不会被垃圾回收(GC)。 - 内存不足时,JVM抛出`OutOfMemoryError`而非回收强引用对象。 - **示例**: ```java Object obj = new Object(); // 强引用 ``` - **使用场景**:常规对象创建(如业务逻辑核心对象)[^1][^2]。 --- #### 2. **软引用(Soft Reference)** - **定义**:通过`SoftReference`类实现,描述非必需但有用的对象。 - **特性**: - 内存充足时保留对象; - **内存不足时(GC前)**,被软引用指向的对象会被回收。 - 常配合`ReferenceQueue`跟踪回收状态[^3][^5]。 - **示例**: ```java SoftReference<String> softRef = new SoftReference<>("Cache Data"); System.out.println(softRef.get()); // 内存充足时可获取对象 ``` - **使用场景**:内存敏感的缓存(如图片、网页缓存)[^3][^5]。 --- #### 3. **弱引用(Weak Reference)** - **定义**:通过`WeakReference`类实现,强度低于软引用。 - **特性**: - 无论内存是否充足,**下一次GC时对象即被回收**。 - 常用于防止内存泄漏(如`WeakHashMap`的键)[^3][^4]。 - **示例**: ```java WeakReference<String> weakRef = new WeakReference<>("Temporary Data"); System.gc(); // GC后 weakRef.get() 返回 null ``` - **使用场景**:弱键缓存、监听器清理[^2][^4]。 --- #### 4. **虚引用(Phantom Reference)** - **定义**:通过`PhantomReference`类实现,最弱的引用类型。 - **特性**: - **无法通过虚引用获取对象**(`get()`始终返回`null`)。 - 对象被GC回收后,虚引用被加入`ReferenceQueue`,用于触发后续操作。 - **示例**: ```java ReferenceQueue<Object> queue = new ReferenceQueue<>(); PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue); // 对象回收后,phantomRef进入queue ``` - **使用场景**: - 堆外内存清理(如NIO的`DirectByteBuffer`)[^4]; - GC回收跟踪及定制化清理[^2][^4]。 --- ### 关键对比表 | 引用类型 | 回收时机 | 是否可获取对象 | 典型应用场景 | |------------|--------------------------|----------------|----------------------| | **强引用** | 永不回收(除非无引用) | 是 | 核心业务对象 | | **软引用** | 内存不足时 | 是 | 内存敏感缓存 | | **弱引用** | 下一次GC时 | 是 | 弱键缓存(如WeakHashMap) | | **虚引用** | 对象回收后通知 | 否 | 资源清理跟踪 | ### 引用队列(ReferenceQueue)的作用 所有非强引用均可关联`ReferenceQueue`: - 当引用指向的对象被GC回收后,引用本身会被加入队列; - 程序可通过轮询队列,执行后续清理逻辑(如关闭资源)[^3][^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值