java Reference 引用学习总结

1、Java引用的类型

强引用、软引用(java.lang.ref.SoftReference)、弱引用(java.lang.ref.WeakReference)、虚引用(java.lang.ref.PhantomReference)。

java默认的引用类型为强引用,比如 Object a = new Object();其中 a 为强引用,new Object()为一个具体的对象。

至于软应用,弱引用,虚引用,就是 JAVA 虚拟机管理对象的范畴了,可以这样理解,SoftReference、WeakReference、PlantomReference 只是一种标记,JAVA 虚拟机在垃圾回收时,对上述不同的标记【引用的对象】采取不同的措施。采取措施如下:

软引用(SoftReference):当内存足够时,该引用【引用的对象】不会被回收,那什么是内存足够呢?进行年轻代的垃圾回收不会触发SoftReference所指向对象的回收,如果触发Full GC,那SoftReference所指向的对象将被回收。

弱引用(WeakReference) :当进行年轻代垃圾回收时,该引用指向的对象,就会被回收。

虚引用(PhantomeReference) 该引用指向的对象,无法对垃圾收集器收集对象时产生任何影响,唯一有用的是,如果被垃圾收集器收集的对象,被PhantomeReference标记,垃圾收集器会通过注册在PhantomeReference上的队列来通知应用程序,该引用指向的对象,已经被垃圾收集器回收。

从上文的描述,也清楚的知道,上述应用是直接JVM打交道,更直接的说是与垃圾回收器直接的交互。

 

2、java.lang.ref.Reference 详解

2.1 关键数据结构

  • private T referent;         /* Treated specially by GC */
  • private ReferenceQueue<? super T> queue;
  • private Reference next;
    非常关键:Reference 本身可以当场一个 Reference 链表使用,在 ReferenceHandler 线程中从 pending 队列中,取出一个Reference, 如果该 Reference 相关的 queue 不为null,则执行入队操作,r.queue.enqueue(r); 参数为当前的引用,在入队列操作时,只要第一次进入队列,该引用的queue会被设置为 ReferenceQueue.ENQUEUE,也就算是再次调用进入队列操作,此时也无法再次与构造方法中传入的队 列绑定   在一起了。
  • private static Reference pending = null
    关键中的关键;此队列维护着需要进入通知队列的引用,由 JVM 虚拟机垃圾回收器在检测到被引用指向的对象可达性发生改变后,如果该对象的引用(Referecnce)注册了引用队列 (ReferenceQueue),则 JVM 虚拟机垃圾收集器会将该引用加入到 pending 队列,注意这个 pending 队列是一个静态类变量。

    为了便于理解上述的观点,先展示一下引用如何使用。

    SoftReference sf = new SoftReference( new Object() );

    其中sf 为引用,new Object为 sf指向的对象,其实也就是建立了 sf 到 new Object 对象的引用(关联),然后垃圾回收器发现 new Object 的可达性发生变化(其实就是变为不可达后),此时JVM虚拟机会根据引用对象 sf 的 queue 是否为空,如果为空,则直接将引用的状态变为 InActivie(非激活,离真正回收不远了)
  •  ReferenceQueue queue = new ReferenceQueue();
    如果 SoftReference sf2 = new SoftRerence( new Object(),  queue  );如果垃圾回收器检测到 new Object 的可达性发生变化后,会将该引用添加到 pending 引用链上,然后有专门的线程 ReferenceHandle 线程来将引用加入到引用链中(入队),也就是应用程序可以从 queue 中获取到所以垃圾回收器回收的对象的应用,也就是 queue是 垃圾回收器通知应用程序 被引用指向的对象已经被垃圾回收的消息。

2.2 Reference 的状态

  • Active
    激活状态(可达),一般新建的引用就是该状态,该状态的属性特点  next = null; queue = ReferenceQueue.Null(默认值) 或者  构造方法指定的 queue   Reference( T referent, ReferenceQueue queue)。

    当垃圾回收器检测到可达性发生变化(变为不可达时),如果 queue == ReferenceQueue.Null 的话,状态直接变为 InActive, 如果 queue 不为空,则加入到 Reference 的静态变量 pending 的队列中,并将状态设置为 Pending。
  •  Pending
    会有一个专门的线程 ReferenceHandler 来处理pengding链表中的引用[pending链表,应该是后进先出的特点],将该引用入队(如果有注册队列,也可以看出是垃圾回收器以此来通知应用程序做些事情【请参考 WeakHashMap 的实现】)。在r.queue.enque(r) 方法中,有个关键点,保证一个引用,只能入队一次,入队后,该引用就与原来的引用队列失去关联;为了清晰展示次过程,将该代码附加上(来源于 java.lang.ref.Reference)。
     
    boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
    
            synchronized (r) {
    
                if (r.queue == ENQUEUED) return false; //关注这里
    
                synchronized (lock) {
    
                    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
    进入队列中的Reference 中的 next 为队列中一个引用,或等于this(表示当前引用为最后一个), queue = ReferenceQueue.ENQUEUE。
     
  • InActive

    queue = ReferenceQueue.NULL; next = this

JAVA 四种引用的理解就到这了,其实 JAVA 中还有一种引用,java.lang.ref.FinalReference 应用,不过修饰符是 default, 包访问权限,主要用于 finalizer方法的执行,请关注下一篇博文。

 

再统一聊聊 java 引用中涉及到的引用的几个队列。

Reference中涉及到的队列(链表)

Reference next;

private static Reference pending = null;

private ReferenceQueue queue;

每个引用可以关联一个引用队列,该引用队列由应用程序创建的,,然后垃圾回收器在检测到引用不可达时,将该引用加入到该队列,应用程序可以根据该引用队列来做些处理。(也就是该引用队列 成为 垃圾回收器与应用程序的通信机制)。

ReferenceQueue 自身的结构

private volatile Reference<? extends T> head = null;

首先,应用程序如下使用引用:

public class TestReference {

           private static ReferenceQueue aQueue = new ReferenceQueue();

           public static void main(String args) {

                  Object a = new Object();   // 代码1

                  WeakReference ref = new WeakReference( a, aQueue );  

           }

   }

然后在程序运行过程,内存不断消耗,直至触发垃圾回收操作。此时,垃圾收集器发现 代码1处的 a 所指向的对象,只有 ref引用它,从根路径不可达,故垃圾回收器,会将 ref 引用加入到  static Reference pending 链表中。【注意,此代码是写在JVM实现中的】

所处理的操作无非就是【 1、如果pending 为空,则将当前引用(ref) 设置为pengding,,并且将 ref对象的next指针指向自己; 如果pending不为空,则将当前的引用(ref)的next指向pengding,然后pengding = 当前的引用ref                   】,所以 pengding 其实就是 一个后进新出的链表单向链表结构。

  • 由此总结出  ref 与 pengding链表关联的第一步  由JVM垃圾回收器完成。

从pengding 链表中取出引用,进行入队操作。该操作由专门的线程(ReferenceHandle 线程处理),我重点将 ReferenceHandle线程的源代码贴出已供分析。

private static class ReferenceHandler extends Thread {

        ReferenceHandler(ThreadGroup g, String name) {

            super(g, name);

        }



        public void run() {

            for (;;) {



                Reference r;

                synchronized (lock) {

                    if (pending != null) {        // 如果pengding不为空,则取出pengding 的第一个引用,然后重新设置pengding 的值(为原来的pending.next,见如下代码   a,b,c)

                        r = pending;                // a 将pending取出,准备入队操作

                        Reference rn = r.next;  // b 先获取原先pending 的 next

                        pending = (rn == r) ? null : rn;  // c  如果pending的next等于本身,则设在pending为空,否则为链表的下一个。//  从这里更加看出 pending 是后进先出队列。

                        r.next = r;

                    } else { // 如果 pending 为空,则线程阻塞,等待垃圾回收器添加新的引用到 pending链表中

                        try {

                            lock.wait();

                        } catch (InterruptedException x) { }

                        continue;

                    }

                }



                // Fast path for cleaners

                if (r instanceof Cleaner) {

                    ((Cleaner)r).clean();

                    continue;

                }



                ReferenceQueue q = r.queue;

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

            }

        }

    }


参考了如下几篇非常优秀的博文,再次表示感谢:

http://hongjiang.info/java-referencequeue/

http://blog.csdn.net/wangyuexiongqi/article/details/38588331

---
见文如面,我是威哥,热衷于成体系剖析JAVA主流中间件,关注公众号『中间件兴趣圈』,回复专栏可获取成体系专栏导航,回复资料可以获取笔者的学习思维导图。



 

    

 

 

 

 

 

      

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

中间件兴趣圈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值