zoukankan      html  css  js  c++  java
  • Java源码阅读------Reference

    描述

    Reference是PhantomReference(虚引用),WeakReference(弱引用),SoftReference(软引用)的父类。位于java.lang.ref包中,是一个使用泛型的抽象类。它定义了所有引用对象的一般操作,与垃圾回收机制(gc)息息相关。

    四种状态

    每一个Reference实例都处在四种可能的内在状态之一。

    Active

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

    活跃状态,新创建的实例,或是还有强引用的实例是处于Active状态,在gc处理时,会根据是否注册引用队列来区分处理。注册了的会将其放到pending状态,没有注册的直接转到Inactive。

    Pending

    /*Pending: An element of the pending-Reference list, waiting to be
     *     enqueued by the Reference-handler thread.  Unregistered instances
     *     are never in this state.
     */
    

    等待状态,在pending状态中,引用会等待Reference-handler这个线程的处理,将其入队进入引用队列ReferenceQueue中,进入Enqueued状态,没有注册引用队列的自然不会进入这一状态。

    Enqueued

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

    队内状态,在这一状态下,引用对象将会处于ReferenceQueue中,如果从中移除后将会进入Inactive状态,同理没有注册ReferenceQueue的引用对象无法进入这一状态。

    Inactive

    /*Inactive: Nothing more to do.  Once an instance becomes Inactive its
     *     state will never change again.
     */ 
    

    不活跃状态,最终状态,位于这一状态的实例将会被gc回收。

    内部实现

    referent

    private T referent;         /* Treated specially by GC */
    

    对内部对象的保存引用,使用了泛型,gc会特别处理。

    public T get() {
        return this.referent;
    }
    

    相关的get方法获取了包装的引用。

    public void clear() {
    	this.referent = null;
    }
    

    使用clear方法可以对referent 清理,之后其将不会再被加入引用队列,gc在回收清理时并不执行该方法,而是更直接的清理。

    queue

    volatile ReferenceQueue<? super T> queue;
    

    对于需要通知机制的,用于储存传入的引用队列,使用通配符泛型,内部可以是相关的子类。

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

    这一方法可以判断queue中是否含有引用。

    public boolean enqueue() {
        return this.queue.enqueue(this);
    }
    

    将注册了引用队列的引用加入其对应的队列中。

    构造函数

    有两种构造函数分别实现需要引用队列的和不需要引用队列的。

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

    说明一下在ReferenceQueue的定义中有两个静态变量NULL与ENQUEUED分别表示没有设置引用队列和已经入队的两种状态。

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

    初始化的静态处理

    线程

    线程:首先,线程不是进程,一个进程有很多子线程,线程是进程中的一个执行流程,只是轮换执行的过程不是同时。多进程是操作系统同时运行多个程序的方法,多线程是程序同时运行多个处理的方法,这里的同步是我们的感觉同步,实际上还是碎片化时间轮流处理。在java中以Thread类实现。
    线程组:用于管理线程,是线程的组合,除了初始线程组外,每个线程组都有一个父线程组,所有的线程组呈树状分布,线程组中除了包含线程还会包含线程组。
    线程优先级:根据线程处理的事务不同给它们分配不同的优先级,优先级越高越可能被执行,在Thread中定义最小(MIN_PRIORITY)为1,最大(MAX_PRIORITY)为10,默认的优先级(NORM_PRIORITY)为5。
    线程分类:一般分为守护线程与用户线程,两者的区别主要体现在java虚拟机的处理上,守护线程依赖非守护线程而存在,如果不存在非守护线程只剩下守护线程,那么虚拟机会直接将其终止并退出(工作完成)。

    静态处理

    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);
    		}
    	});
    }
    

    首先获取了当前正在执行的线程的线程组。之后使用循环获得根线程组,应该是System线程组。之后初始化了一个ReferenceHandler线程,这个是Reference的一个内部类,将名为Reference Handler的线程注册到根线程组中,并设置优先级为最高的10,并设置为守护进程运行,必须在运行之前设置。
    最后说一下SharedSecrets类,这个类的实现只是包含了许多接口的get与set,简单的说是实现了私有方法的共享,而不必使用反射去实现,在这里其实现了JavaLangRefAccess这个接口,共享了tryHandlePending这一私有方法。

    ReferenceHandler

    ReferenceHandler是一个静态内部类,继承自Thread是一个线程,负责将处于pending状态的引用加入引用队列。

    构造函数

    ReferenceHandler(ThreadGroup g, String name) {
    	super(g, name);
    }
    

    调用Thread中的方法将线程加入到线程组中。

    静态处理

    static {
    	// pre-load and initialize InterruptedException and Cleaner classes
    	// so that we don't get into trouble later in the run loop if there's
    	// memory shortage while loading/initializing them lazily.
    	ensureClassInitialized(InterruptedException.class);
    	ensureClassInitialized(Cleaner.class);
    }
    

    使用ensureClassInitialized方法完成InterruptedException与Cleaner两个类的初始化加载。

    ensureClassInitialized

    很简单的实现,通过传入的类获取类名加载对应的类,参数中的true为实现初始化,getClassLoader为获取传入的类中的类加载器。

    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);
        }
    }
    

    run

    这个方法大家应该很熟悉,线程的运行方法包含运行进行的操作。

    public void run() {
    	while (true) {
    		tryHandlePending(true);
    	}
    }
    

    实现也很直观就是跑tryHandlePending这个函数。

    tryHandlePending

    应该算是这个类中比较有意思的一个函数了,负责将处于Pending状态的Reference加入到引用队列queue中。若返回为true表示还有处于Pending状态的Reference,为false则没有,可以通过返回值来操控其运行。waitForNotify参数表示是否等待Notify方法,当没有Pending状态的引用时waitForNotify为true则等待Notify方法或线程的中断interrupted,为false就立即结束。

    Object锁

    static private class Lock { }
    private static Lock lock = new Lock();
    

    这里定义了一个Object用于线程同步,具体可以参考Object中的notify与wait方法Java源码阅读------Object

    数据结构

    private static Reference<Object> pending = null;
    transient private Reference<T> discovered;  /* used by VM */
    Reference next;
    

    在实现中出现了两个数据结构,一个是负责描述入队后引用的单向列表由next指向下一个Reference对象,具体的是应用在ReferenceQueue实现,还有一个是Pending队列用于存储所有Pending状态的Reference,由discovered与pending实现分别指向队列首部与相应节点的下一个元素。

    详细实现

    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;
    }
    

    判断pending是否为空,为空则根据waitForNotify参数来判断是否立即返回。若不为空,用r取出Pending中的首部元素,重新将Pending指向其下一个元素,判断r是否为Cleaner类方便进行单独的处理,如果是就用c来存储,跳出同步操作,根据c中的数据进行单独的Cleaner清理,最后将获得的Pending状态的引用加入到对应的ReferenceQueue中。注意在同步操作部分可能会出现内存溢出,所以使用了try-catch,出现异常时默认返回true。

    小结

    Reference的使用很灵活,其运行过程与gc处理息息相关,源码中的思想还是很值得研究的。

  • 相关阅读:
    conda 激活环境失败解决办法
    openSmile-2.3.0在Linux下安装
    Ubuntu16.04下安装多版本cuda和cudnn
    几个最新免费开源的中文语音数据集
    train loss与test loss结果分析
    文件路径
    Properties类与配置文件
    内省
    Junit单元测试
    Hdfs常用命令
  • 原文地址:https://www.cnblogs.com/yanzs/p/13788280.html
Copyright © 2011-2022 走看看