美文网首页
Reference 和 Reference queue 精髓源码

Reference 和 Reference queue 精髓源码

作者: 良辰夜 | 来源:发表于2018-07-13 17:07 被阅读0次

目录

  1. Reference 状态
    1.1 Active
    1.2 Pending
    1.3 Enqueued
    1.4 Inactive
  2. reference-hannel 线程
    2.1 启动线程
    2.2 线程执行

1.Reference 状态

每个Reference状态列表是Active、Pending、Enqueued、Inactive (本文也将基于以下这四种状态做出源码解读)

状态间主要的标识:
用Reference的成员变量queue与next(类似于单链表中的next)来标识

ReferenceQueue<? super T> queue;
Reference next; //当状态不为active,gc自动赋值为this

有了这些约束,GC 只需要检测next字段就可以知道是否需要对该引用对象(reference)采取特殊处理

  • 如果next为null,那么说明该引用为Active状态
  • 如果next不为null,那么 GC 应该按其正常逻辑处理该引用。

什么特殊处理?
GC在状态active发送变化时,会主动把 weakReference中的 reference置null,PhantomReference 中的 reference 无视 (PhantomReference的get方法重写返回null,可能就因为这原因就没有置null了),并给next赋值this,以及pending等。(别问我为啥,测出来的。。。。培训出来就这水平了)

1.1 Active

描述:
active 状态下的reference会受到GC的特别对待,GC在发现对象的可达性变成合适的状态后将变更reference状态,具体是变更为pending还是Inactive,取决于是否注册了 queue

特点:

//如果构造参数中没指定queue,那么queue为ReferenceQueue.NULL
//否则为构造参数中传递过来的queue
queue = ReferenceQueue || ReferenceQueue.NULL
next = null

源码分析:

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

1.2 Pending

描述:
pending-Reference列表中的引用都是这个状态。
它们等着被内部线程ReferenceHandler处理(调用ReferenceQueue.enqueue方法)
没有注册的实例不会进入这个状态!

特点:

//ReferenceQueue 是注册的队列,从构造参数哪里传过来的
queue=ReferenceQueue
next = this

源码分析:

  1. 想进Pending就必须要注册队列,ReferenceQueue.NULL 是不会enqueue
 static boolean tryHandlePending(boolean waitForNotify) {
       //some code
        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }
  1. next=this,这个应该是GC赋的值,只要reference的state一改变,这个next就莫名其妙被赋值了。

3.似乎明白了 ReferenceQueue.ENQUEUED 的含义

public boolean isEnqueued() {
        return (this.queue == ReferenceQueue.ENQUEUED);
    }

1.3 Enqueued

描述:
调用ReferenceQueue.enqueued方法后的引用处于这个状态中。
没有注册的实例不会进入这个状态。

特点:

queue = ReferenceQueue.ENQUEUED;
next = queue的下一个引用, 如果已经queue是最后一个元素,那么就是自身.

源码分析:
可以看出 整体是一种先进后出的栈结构。head作为栈头。r.next指向成员变量 head,如果head为null,那么就指向自己(为了后面的压栈好找元素)

private volatile Reference<? extends T> head = null;
boolean enqueue(Reference<? extends T> r) { 
//some code
            r.queue = ENQUEUED;
            r.next = (head == null) ? r : head;
            head = r;
//some code            
}

1.4 Inactive

描述:
最终状态,处于这个状态的引用对象,状态不会在改变。

两种途径到这个状态:
1.referenceQueue 调用 reallyPoll ,
2.不注册 referenceQueue,reference state变化时。

特点:

queue = ReferenceQueue.NULL;
next = this

源码分析:

private Reference<? extends T> reallyPoll() {       /* Must hold lock */
        Reference<? extends T> r = head;
        //some code
            r.queue = NULL;
            r.next = r;
         //some code
    }

2.reference-hannel 线程

2.1启动线程

  1. 说明jvm一启动线程就跟着启动了
  2. 提供了一个 JavaLangRefAccess 来提供外接接口访问
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();

        // provide access in SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {//提供一个访问接口
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }

2.2 线程执行

  1. pending == null 时,会阻塞线程,所以不会一直调用。gc肯定notify了
  2. Cleaner 是个重要的接口,非常适合做资源回收。
  3. 如果注册了引用队列,会将引用enqueue进去。
static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                if (pending != null) {
                    r = pending;
                    // 'instanceof' might throw OutOfMemoryError sometimes
                    // so do this before un-linking 'r' from the 'pending' chain...
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    // unlink 'r' from 'pending' chain
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    // The waiting on the lock may cause an OutOfMemoryError
                    // because it may try to allocate exception objects.
                    if (waitForNotify) {
                        lock.wait();
                    }
                    // retry if waited
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            // Give other threads CPU time so they hopefully drop some live references
            // and GC reclaims some space.
            // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
            // persistently throws OOME for some time...
            Thread.yield();
            // retry
            return true;
        } catch (InterruptedException x) {
            // retry
            return true;
        }

        // Fast path for cleaners
        if (c != null) {
            c.clean();
            return true;
        }

        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }

引用:
https://liujiacai.net/blog/2015/09/27/java-weakhashmap/#%E5%BC%B1%E5%BC%95%E7%94%A8%EF%BC%88weak-reference%EF%BC%89

相关文章

网友评论

      本文标题:Reference 和 Reference queue 精髓源码

      本文链接:https://www.haomeiwen.com/subject/vubjpftx.html