一提到Reference 百分之九十九的java程序员都懵逼了

原来的标题是:"一提到Reference 99.99%的java程序员都懵逼了",为啥改成汉字了呢?吐槽一下,因为CSDN出bug了,如果你用了%做标题,你的文章就别想用它的编辑器修改了,它的js脚本写的不够健壮,报错了;


java.lang.ref.Reference

java程序员都知道如果一个对象没有任何引用了,那么这个对象在gc的时候就被回收了,大部分java程序员是基于我们常见的强引用去理解引用的,强引用这种方式虽然简单直接,但是对于一些特殊的java对象,如缓存数据在内存紧张时自动释放掉空间防止oom、直接内存对象回收之前需自动释放掉其占用的堆外内存,当socket对象被回收之前关闭连接,当文件流对象被回收之前自动关闭打开的文件等操作,强引用就无能为力了,为了实现这些特殊需求,java还引入了除了强引以外的引用类型来辅助对象回收操作 或 监控对象的生存周期。

先来看看引用类型的大家族:

在java.lang.ref包下除了ReferenceQueue类,Finalizer、FinalReference、PhantomReference、SoftReference、WeakReference都直接或间接继承了Reference类,想想为什么没有强引用StrongReference?



理解Reference的关键点在于:

1. 两个队列pending与ReferenceQueue的作用;

2. Reference对象4种状态的转换;

3. ReferenceHander线程的处理过程,它是如何将Pending与ReferenceQueue关联起来的;

4. Pending队列数据来源;

理解了以上4个问题,才算真不再对Reference懵逼了,下面就带着这四个问题去看下面的内容。

Reference构造方法

我们先来看看这些引用类型的父类都给我们提供了什么?

其内部提供2个构造函数,除了传入referent对象外,区别就是要不要传入一个ReferenceQueue队列

  Reference(Treferent) {

        this(referent,null);

   }

 

   Reference(T referent,ReferenceQueue<?super T>queue){

        this.referent =referent;

        this.queue = (queue==null)? ReferenceQueue.NULL:queue;

    }

ReferenceQueue队列的作用就是Reference引用的对象被回收时,Reference对象能否进入到pending队列,最终由ReferenceHander线程处理后,Reference就被放到了这个队列里面(Cleaner对象除外,后面有一篇专门讲Cleaner对象源码),然后我们就可以在这个ReferenceQueue里拿到reference,执行我们自己的操作(至于什么操作就看你想怎么用了),所以这个队列起到一个对象被回收时通知的作用;

如果不带ReferenceQueue的话,要想知道Reference持有的对象是否被回收,就只有不断地轮训reference对象,通过判断里面的get是否为null(phantomReference对象不能这样做,其get始终返回null,因此它只有带queue的构造函数).这两种方法均有相应的使用场景,取决于实际的应用.如weakHashMap中就选择去查询queue的数据,来判定是否有对象将被回收.而ThreadLocalMap,则采用判断get()是否为null来作处理;

对于带ReferenceQueue参数的构造方法,如果传入的队列为null,那么就会给成员变量queue赋值为ReferenceQueue.NULL队列,这个NULL是ReferenceQueue对象的一个继承了ReferenceQueue的内部类,它重写了入队方法enqueue,这个方法只有一个操作,直接返回 false,也就是这个对列不会存取任何数据,它起到状态标识的作用;


Reference的重要成员变量

//在构造方法传入java对象最后就赋值给了referent,也就是它引用到的对象

private Treferent;        /* Treated specially by GC */

  //pending队列, pending成员变量与后面的discovered对象一起构成了一个pending单向链表,注意这个成员变量是一个静态对象,所以是全局唯一的,pending为链表的头节点,discovered为链表当前Reference节点指向下一个节点的引用,这个队列是由jvm的垃圾回收器构建的,当对象除了被reference引用之外没有其它强引用了,jvm的垃圾回收器就会将指向需要回收的对象的Reference都放入到这个队列里面(好好理解一下这句话,注意是指向要回收的对象的Reference,要回收的对象就是Reference的成员变量refernt持有的对象,是refernt持有的对象要被回收,而不是Reference对象本身),这个队列会由ReferenceHander线程来处理(ReferenceHander线程是jvm的一个内部线程,它也是Reference的一个内部类,它的任务就是将pending队列中要被回收的Reference对象移除出来,如果Reference对象在初始化的时候传入了ReferenceQueue队列,那么就把从pending队列里面移除的Reference放到它自己的ReferenceQueue队列里(为什么是它自己的?pending队列是全局唯一的队列,但是Reference的queue却不是,它是在构造方法里面指定的,前面说过这里Cleaner对象是个特例),如果没有ReferenceQueue队列,那么其关联的对象就不会进入到Pending队列中,会直接被回收掉,除此之外ReferenceHander线程还会做一些其它操作,后面会讲到

 /* List of References waiting to beenqueued.  The collector adds

     * References to this list, while theReference-handler thread removes

     * them. This list is protected by the above lock object. The

     * list uses the discovered field to linkits elements.

     */

    privatestatic Reference<Object>pending =null;

  //与成员变量pending一起组成pending队列,指向链表当前节点的下一个节点

   /* When active:   next element in a discovered reference listmaintained by GC (or this if last)

     *    pending:   next element in thepending list (or null if last)

     *  otherwise:   NULL

     */

transientprivate Reference<T>discovered/* used by VM */

 

//ReferenceQueue队列,这就是我们前面提到的起到通知作用的ReferenceQueue,需要注意的是ReferenceQueue并不是一个链表数据结构,它只持有这个链表的表头对象header,这个链表是由Refence对象里面的next成员变量构建起来的,next也就是链表当前节点的下一个节点(只有next的引用,它是单向链表),所以Reference对象本身就是一个链表的节点,这个链表数据来源前面讲pending队列的时候已经提到了,它是由ReferenceHander线程从pending队列中取的数据构建的(需要注意的是,这个对象并不是一个全局的,它是在构造方法里面传入进来的,所以Reference对象需要进入那个队列是我们自己指定的,也有特例,如FinalReference,Cleaner就是一个内部全局唯一的,无法指定,后面有两篇专门讲他们俩的源码),一旦Reference对象放入了队列里面,那么queue就会被设置为ReferenceQueue.ENQUEUED,来标识当前Reference已经进入到队里里面了;

   volatileReferenceQueue<?super T>queue;

 

  //用来与queue成员变量一同组成ReferenceQueue队列,见上面queue的说明;

   /* When active:   NULL

     *     pending:  this

     *   Enqueued:   next reference inqueue (or this if last)

     *   Inactive:   this

     */

   @SuppressWarnings("rawtypes")

   Reference next;

 

 

 

    //lock成员变量是pending队列的全局锁,如果你搜索这个lock变量会发现它只在ReferenceHander线程run方法里面用到了,不要忘了jvm垃圾回收器线程也会操作pending队列,往pending里面添加Reference对象,所以需要加锁;

   /* Object used to synchronize with thegarbage collector.  The collector

     * must acquire this lock at the beginningof each collection cycle.  It is

     * therefore critical that any code holdingthis lock complete as quickly

     * as possible, allocate no new objects,and avoid calling user code.

     */

   staticprivateclass Lock { };

   privatestatic Locklock =new Lock();


这里一定要理解pending队列,什么样的Reference对象会进入这个队列。

进入这个队列的Reference对象需要满足两个条件:

1.    Reference所引用的对象已经不存在其它强引用;

2.    Reference对象在创建的时候,指定了ReferenceQueue



Reference状态及其转换

如果去查看Reference源码,会发现这个类的开头有一段很长的注释,说明了Reference对象的四种状态:

/* A Reference instance is in one of four possible internal states:
     *
     *     Active: Subject to special treatment by the garbage collector.  Some
     *     time after the collector detects that the reachability of the
     *     referent has changed to the appropriate state, it changes the
     *     instance's state to either Pending or Inactive, depending upon
     *     whether or not the instance was registered with a queue when it was
     *     created.  In the former case it also adds the instance to the
     *     pending-Reference list.  Newly-created instances are Active.
     *
     *     Pending: An element of the pending-Reference list, waiting to be
     *     enqueued by the Reference-handler thread.  Unregistered instances
     *     are never in this state.
     *
     *     Enqueued: An element of the queue with which the instance was
     *     registered when it was created.  When an instance is removed from
     *     its ReferenceQueue, it is made Inactive.  Unregistered instances are
     *     never in this state.
     *
     *     Inactive: Nothing more to do.  Once an instance becomes Inactive its
     *     state will never change again.
     *
     * The state is encoded in the queue and next fields as follows:
     *
     *     Active: queue = ReferenceQueue with which instance is registered, or
     *     ReferenceQueue.NULL if it was not registered with a queue; next =
     *     null.
     *
     *     Pending: queue = ReferenceQueue with which instance is registered;
     *     next = this
     *
     *     Enqueued: queue = ReferenceQueue.ENQUEUED; next = Following instance
     *     in queue, or this if at end of list.
     *
     *     Inactive: queue = ReferenceQueue.NULL; next = this.
     *
     * With this scheme the collector need only examine the next field in order
     * to determine whether a Reference instance requires special treatment: If
     * the next field is null then the instance is active; if it is non-null,
     * then the collector should treat the instance normally.
     *
     * To ensure that a concurrent collector can discover active Reference
     * objects without interfering with application threads that may apply
     * the enqueue() method to those objects, collectors should link
     * discovered objects through the discovered field. The discovered
     * field is also used for linking Reference objects in the pending list.
     */

如果你没有耐心看完这段注释,请直接往后看:

1.       Active:活动状态,对象存在强引用状态,还没有被回收;

2.       Pending:垃圾回收器将没有强引用的Reference对象放入到pending队列中,等待ReferenceHander线程处理(前提是这个Reference对象创建的时候传入了ReferenceQueue,否则的话对象会直接进入Inactive状态);

3.       Enqueued:ReferenceHander线程将pending队列中的对象取出来放到ReferenceQueue队列里;

4.       Inactive:处于此状态的Reference对象可以被回收,并且其内部封装的对象也可以被回收掉了,有两个路径可以进入此状态,

路径一:在创建时没有传入ReferenceQueue的Reference对象,被Reference封装的对象在没有强引用时,指向它的Reference对象会直接进入此状态;

路径二、此Reference对象经过前面三个状态后,已经由外部从ReferenceQueue中获取到,并且已经处理掉了。

Reference对象的状态只需要通过成员变量next和queue来判断:

1.    Active:   next=null

2.    Pending:  next = this ,queue = ReferenceQueue

3.    Enqueued:  queue =ReferenceQueue.ENQUEUED

4.    Inactive:    next = this  ,queue = ReferenceQueue.NULL; 

以下是Reference对象状态转换图,用word画的,将就着看吧:

特例还是要拿出来单独说,上面的图不适用描述Cleaner对象,Cleaner对象是没有Enqueue状态的,它经过HandReference处理时执行其clean方法清理,然后就直接进入了inactive状态了;

下面简单看一下他们的源码实现,如果你理解了上面所有内容,下面的源码理解起来非常简单,我只做简单的注释说明:

ReferenceHandler线程源码解析

ReferenceHandler线程是一个拥有最高优先级的守护线程,它是Reference类的一个内部类,在Reference类加载执行cinit的时候被初始化并启动;它的任务就是当pending队列不为空的时候,循环将pending队列里面的头部的Reference移除出来,如果这个对象是个Cleaner实例,那么就直接执行它的clean方法来执行清理工作;否则放入到它自己的ReferenceQueue里面;

所以这个线程是pending队列与ReferenceQueue的桥梁;


/* High-priority thread to enqueue pending References*/

   private static class ReferenceHandler extends Thread {

 

       ReferenceHandler(ThreadGroup g, String name) {

           super(g, name);

       }

 

       public void run() {

           for (;;) {

                //从pending中移除的Reference对象

               Reference<Object> r;

               //此处需要加全局锁,因为除了当前线程,gc线程也会操作pending队列

               synchronized (lock) {

                    //如果pending队列不为空,则将第一个Reference对象取出

                    if (pending != null) {

                        //缓存pending队列头节点

                        r = pending;

                        //将头节点指向discovered,discovered为pending队列中当前节点的下一个节点,这样就把第一个头结点出队了

                        pending = r.discovered;

                        //将当前节点的discovered设置为null;当前节点出队,不需要组成链表了;

                        r.discovered = null;

                    } else {//如果pending队列为空,则等待

                        try {

                            try {

                                lock.wait();

                            } catch(OutOfMemoryError x) { }

                        } catch(InterruptedException x) { }

                        continue;

                    }

               }

               // 如果从pending队列出队的r是一个Cleaner对象,那么直接执行其clean()方法执行清理操作;

               if (r instanceof Cleaner) {

                    ((Cleaner)r).clean();

                   //注意这里,这里已经不往下执行了,所以Cleaner对象是不会进入到队列里面的,给它设置ReferenceQueue的作用是为了让它能进入Pending队列后被ReferenceHander线程处理;

                    continue;

                }

               //将对象放入到它自己的ReferenceQueue队列里

               ReferenceQueue<Object> q = r.queue;

               if (q != ReferenceQueue.NULL) q.enqueue(r);

           }

       }

    }

   //以下是ReferenceHander线程初始化并启动的操作

   static {

        ThreadGroup tg =Thread.currentThread().getThreadGroup();

        for (ThreadGroup tgn = tg;

             tgn != null;

             tg = tgn, tgn = tg.getParent());

       //线程名称为Reference Handler

        Thread handler = newReferenceHandler(tg, "Reference Handler");

        /* If there were a special system-onlypriority greater than

         * MAX_PRIORITY, it would be used here

         */

       //线程有最高优先级

       handler.setPriority(Thread.MAX_PRIORITY);

        //设置线程为守护线程;

        handler.setDaemon(true);

        handler.start();

}

ReferenceQueue源码解析

ReferenceQueue队列是一个单向链表,ReferenceQueue里面只有一个header成员变量持有队列的队头,Reference对象是从队头做出队入队操作,所以它是一个后进先出的队列

public class ReferenceQueue<T> {

 

   public ReferenceQueue() { }

    //内部类,它是用来做状态识别的,重写了enqueue入队方法,永远返回false,所以它不会存储任何数据,见后面的NULL和ENQUEUED两个标识成员变量

    private static class Null<S> extendsReferenceQueue<S> {

       boolean enqueue(Reference<? extends S> r) {

           return false;

       }

    }

    //当Reference对象创建时没有指定queue或Reference对象已经处于inactive状态

staticReferenceQueue<Object> NULL = new Null<>();

//当Reference已经被ReferenceHander线程从pending队列移到queue里面时

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

 

staticprivate class Lock { };

//出队入队时对队列加锁

privateLock lock = new Lock();

//队列头

privatevolatile Reference<? extends T> head = null;

//队列长度

   private long queueLength = 0;

    //入队操作,ReferenceHander调用此方法将Reference放入到队列里

   boolean enqueue(Reference<? extends T> r) { /* Called only byReference class */

          //加锁操作队列

synchronized(lock) {

           //如果Reference创建时没有指定队列或Reference对象已经在队列里面了,则直接返回

           ReferenceQueue<?> queue = r.queue;

           if ((queue == NULL) || (queue == ENQUEUED)) {

               return false;

           }

                //只有r的队列是当前队列才允许入队

           assert queue == this;

           //将r的queue设置为ENQUEUED状态,标识Reference已经入队

           r.queue = ENQUEUED;

           //从队列头部入队

           r.next = (head == null) ? r : head;

           head = r;

           //队列里面对象数量+1

           queueLength++;

           //如果r是一个FinalReference实例,那么将FinalReference数量也+1

            if (r instanceof FinalReference) {

               sun.misc.VM.addFinalRefCount(1);

/**Vm.addFinalRefCount(int n)方法的源码

  publicstatic void addFinalRefCount(int n) {

       // The caller must hold lock to synchronize the update.

 

       finalRefCount += n;

       if (finalRefCount > peakFinalRefCount) {

           peakFinalRefCount = finalRefCount;

       }

}

可以通过sun.misc.VM.getFinalRefCount()和sun.misc.VM.getPeakFinalRefCount()来获取FinalReference对象的当前数量和峰值数量

***/

           }

                //唤醒出队操作的等待线程

           lock.notifyAll();

           return true;

       }

    }

    //Reference对象出队操作,将头部第一个对象移出队列,并将队列长度-1

   @SuppressWarnings("unchecked")

   private Reference<? extends T> reallyPoll() {       /* Must hold lock */

       Reference<? extends T> r = head;

       if (r != null) {

           head = (r.next == r) ?

               null :

               r.next; // Unchecked due to the next field having a raw type inReference

           r.queue = NULL;

           r.next = r;

           queueLength--;

           if (r instanceof FinalReference) {

               sun.misc.VM.addFinalRefCount(-1);

           }

           return r;

       }

       return null;

    }

 

//将队列头部第一个对象从队列中移除出来,如果队列为空则直接返回null(此方法不会被阻塞)

   public Reference<? extends T> poll() {

       if (head == null)

           return null;

       synchronized (lock) {

           return reallyPoll();

       }

    }

 

   //将头部第一个对象移出队列并返回,如果队列为空,则等待timeout时间后,返回null,这个方法会阻塞线程

   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;

           for (;;) {

               lock.wait(timeout);

               r = reallyPoll();

               if (r != null) return r;

                if (timeout != 0) return null;

           }

       }

    }

   public Reference<? extends T> remove() throws InterruptedException{

       return remove(0);

    }

}



  • 9
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值