详解java中Reference的实现与相应的执行过程

一、Reference类型(除强引用)

可以理解为Reference的直接子类都是由jvm定制化处理的,因此在代码中直接继承于Reference类型没有任何作用.只能继承于它的子类,相应的子类类型包括以下几种.(忽略没有在java中使用的,如jnireference)

SoftReference

WeakReference

FinalReference

PhantomReference

上面的引用类型在相应的javadoc中也有提及.FinalReference专门为finalize方法设计,另外几个也有特定的应用场景.其中softReference用在内存相关的缓存当中,weakReference用在与回收相关的大多数场景.phantomReference用在与包装对象回收回调场景当中(比如资源泄漏检测).

可以直接在ide中查看几个类型的子类信息,即可了解在大多数框架中,都是通过继承相应的类型用在什么场景当中,以便于我们实际进行选型处理.

二、Reference构造函数

其内部提供2个构造函数,一个带queue,一个不带queue.其中queue的意义在于,我们可以在外部对这个queue进行监控.即如果有对象即将被回收,那么相应的reference对象就会被放到这个queue里.我们拿到reference,就可以再作一些事务.

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

相应的构造函数如下所示:

Reference(T referent) {
 this(referent, null);
}

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

这里面的NULL队列,即可以理解为不需要对其队列中的数据作任何处理的队列.并且其内部也不会存取任何数据.

在上面的对象中,referent表示其引用的对象,即我们在构造的时候,需要被包装在其中的对象.对象即将被回收的定义即此对象除了被reference引用之外没有其它引用了(并非确实没有被引用,而是gcRoot可达性不可达,以避免循环引用的问题).

queue即是对象即被回收时所要通知的队列,当对象即被回收时,整个reference对象(而不是被回收的对象)会被放到queue里面,然后外部程序即可通过监控这个queue拿到相应的数据了.

三、ReferenceQueue及Reference引用链

这里的queue名义上是一个队列,但实际内部并非有实际的存储结构,它的存储是依赖于内部节点之间的关系来表达.可以理解为queue是一个类似于链表的结构,这里的节点其实就是reference本身.可以理解为queue为一个链表的容器,其自己仅存储当前的head节点,而后面的节点由每个reference节点自己通过next来保持即可.

Reference状态值

每个引用对象都有相应的状态描述,即描述自己以及包装的对象当前处于一个什么样的状态,以方便进行查询,定位或处理.

1、Active:活动状态,即相应的对象为强引用状态,还没有被回收,这个状态下对象不会放到queue当中.在这个状态下next为null,queue为定义时所引用的queue.

2、Pending:准备放入queue当中,在这个状态下要处理的对象将挨个地排队放到queue当中.在这个时间窗口期,相应的对象为pending状态.不管什么reference,进入到此状态的,即可认为相应的此状态下,next为自己(由jvm设置),queue为定义时所引用的queue.

3、Enqueued:相应的对象已经为待回收,并且相应的引用对象已经放到queue当中了.准备由外部线程来询循queue获取相应的数据.此状态下,next为下一个要处理的对象,queue为特殊标识对象ENQUEUED.

4、Inactive:即此对象已经由外部从queue中获取到,并且已经处理掉了.即意味着此引用对象可以被回收,并且对内部封装的对象也可以被回收掉了(实际的回收运行取决于clear动作是否被调用).可以理解为进入到此状态的肯定是应该被回收掉的.

jvm并不需要定义状态值来判断相应引用的状态处于哪个状态,只需要通过计算next和queue即可进行判断.

四、ReferenceQueue#head

始终保存当前队列中最新要被处理的节点,可以认为queue为一个后进先出的队列.当新的节点进入时,采取以下的逻辑

newE.next = head;head=newE;

然后,在获取的时候,采取相应的逻辑

tmp = head;head=tmp.next;return tmp;

五、Reference#next

即描述当前引用节点所存储的下一个即将被处理的节点.但next仅在放到queue中才会有意义.为了描述相应的状态值,在放到队列当中后,其queue就不会再引用这个队列了.而是引用一个特殊的ENQUEUED.因为已经放到队列当中,并且不会再次放到队列当中.

六、Reference#referent

即描述当前引用所引用的实际对象,正如在注解中所述,其会被仔细地处理.即此什么什么时候会被回收,如果一旦被回收,则会直接置为null,而外部程序可通过通过引用对象本身(而不是referent)了解到,回收行为的产生.

七、ReferenceQueue#enqueue 待处理引用入队

此过程即在reference对象从active->pending->enqued的过程. 此方法为处理pending状态的对象为enqued状态.相应的过程即为之前的逻辑,即将一个节点入队操作,相应的代码如下所示.

r.queue = ENQUEUED;
r.next = (head == null) ? r : head;
head = r;
queueLength++;
lock.notifyAll();

最后的nitify即通知外部程序之前阻塞在当前队列之上的情况.(即之前一直没有拿到待处理的对象)

八、Reference#tryHandlePending

即处理reference对象从active到pending状态的变化.在Reference对象内部,有一个static字段,其相应的声明如下:

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

可以理解为jvm在gc时会将要处理的对象放到这个静态字段上面.同时,另一个字段discovered,表示要处理的对象的下一个对象.即可以理解要处理的对象也是一个链表,通过discovered进行排队,这边只需要不停地拿到pending,然后再通过discovered不断地拿到下一个对象即可.因为这个pending对象,两个线程都可能访问,因此需要加锁处理.

相应的处理过程如下所示:

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;
}
//将处理对象入队,即进入到enqued状态
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);

九、Reference#clear

清除引用对象所引用的原对象,这样通过get()方法就不能再访问到原对象了.从相应的设计思路来说,既然都进入到queue对象里面,就表示相应的对象需要被回收了,因为没有再访问原对象的必要.此方法不会由JVM调用,而jvm是直接通过字段操作清除相应的引用,其具体实现与当前方法相一致.

clear的语义就是将referent置null.

WeakReference对象进入到queue之后,相应的referent为null.

SoftReference对象,如果对象在内存足够时,不会进入到queue,自然相应的reference不会为null.如果需要被处理(内存不够或其它策略),则置相应的referent为null,然后进入到queue.

FinalReference对象,因为需要调用其finalize对象,因此其reference即使入queue,其referent也不会为null,即不会clear掉.

PhantomReference对象,因为本身get实现为返回null.因此clear的作用不是很大.因为不管enqueue还是没有,都不会清除掉.

十、ReferenceHandler enqueue线程

上面提到jvm会将要处理的对象设置到pending对象当中,因此肯定有一个线程来进行不断的enqueue操作,此线程即引用处理器线程,其优先级为MAX_PRIORITY,即最高.相应的启动过程为静态初始化创建,可以理解为当任何使用到Reference对象或类时,此线程即会被创建并启动.相应的代码如下所示:

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

其优先级最高,可以理解为需要不断地处理引用对象.在通过jstack打印运行线程时,相应的Reference Handler即是指在这里初始化的线程,如下所示:

十一、JVM相关

在上述的各个处理点当中,都与JVM的回收过程相关.即认为gc流程会与相应的reference协同工作.如使用cms收集器,在上述的整个流程当中,涉及到preclean过程,也涉及到softReference的重新标记处理等,同时对reference对象的各种处理也需要与具体的类型相关进行协作.相应的JVM处理,采用C++代码,因此需要好好地再理一下.

十二、总结

与finalReference对象相同,整个reference和referenceQueue都是一组协同工作的处理组,为保证不同的引用语义,通过与jvm gc相关的流程一起作用,最终实现不同场景,不同引用级别的处理.

另外,由于直接使用referenceQueue,再加上开启线程去监控queue太过麻烦和复杂.可以参考由google guava实现的 FinalizableReferenceQueue 以及相应的FinalizableReference对象.可以简化一点点处理过程.以上就是这篇文章的全部内容,希望对大家的学习或者工作带来一定的帮助。

(0)

相关推荐

  • Java Reference源码解析

    Reference对象封装了其它对象的引用,可以和普通的对象一样操作,在一定的限制条件下,支持和垃圾收集器的交互.即可以使用Reference对象来引用其它对象,但是最后还是会被垃圾收集器回收.程序有时候也需要在对象回收后被通知,以告知对象的可达性发生变更. Java提供了四种不同类型的引用,引用级别从高到低分别为FinalReference,SoftReference,WeakReference,PhantomReference.其中FinalReference不对外提供使用.每种类型对应着不

  • Java 中 Reference用法详解

    Java  Reference详解 在 jdk 1.2 及其以后,引入了强引用.软引用.弱引用.虚引用这四个概念.网上很多关于这四个概念的解释,但大多是概念性的泛泛而谈,今天我结合着代码分析了一下,首先我们先来看定义与大概解释(引用类型在包 Java.lang.ref 里). 1.强引用(StrongReference) 强引用不会被GC回收,并且在java.lang.ref里也没有实际的对应类型.举个例子来说: Object obj = new Object(); 这里的obj引用便是一个强引

  • Java中使用Preferences 的 API设置用户偏好

    Preferences的中文意思即偏好或喜好的意思,也就是说同一个程序在每次运行完后,可以通过Preferences来记录用户的偏好,下次启动时,程序会利用这些信息来了解用户的喜好.而这些信息个人理解应该就是存储在系统的注册表中. 下面我们来学习一下Java中的Preferences的API; 概述: 本文将介绍自jdk1.4版本后可用的java的Preferences. Java的Preferences API提供系统的方法来处理用户和系统的偏好及数据配置, 例如.保存用户设置,记住一个文本框

  • 分享关于JAVA 中使用Preferences读写注册表时要注意的地方

    要注意的只有一个地方,那就是键名或者项名不要包含大写字母,否则读不到数据. 代码是这样的: 复制代码 代码如下: Preferences preferences = Preferences.systemRoot();String strRegTime = preferences.get("regTime", ""); 注册表中是这样的 不论怎么改都读不到regTime的值.后来想到现在代码中把值写进去看看是什么样的,代码如下 复制代码 代码如下: Preferenc

  • Java使用Preference类保存上一次记录的方法

    本文实例讲述了Java使用Preference类保存上一次记录的方法.分享给大家供大家参考.具体分析如下: 在使用java中JFileChooser选择文件的时候,我们总希望在下次打开的时候能保存上次浏览的记录,即打开文件对话框的时候,总能追溯到上一次的路径. 有一个很愚蠢的方法,那就是在每次打开的时候把选择的文件的路径保存到本地文件中,再打开JFileChooser对话框的时候,先查看是否有内容,如果文件中有内容则按照存储的路径打开对话框. 如果我说Java里面可以不使用JNI的手段操作Win

  • 详解java中Reference的实现与相应的执行过程

    一.Reference类型(除强引用) 可以理解为Reference的直接子类都是由jvm定制化处理的,因此在代码中直接继承于Reference类型没有任何作用.只能继承于它的子类,相应的子类类型包括以下几种.(忽略没有在java中使用的,如jnireference) SoftReference WeakReference FinalReference PhantomReference 上面的引用类型在相应的javadoc中也有提及.FinalReference专门为finalize方法设计,另

  • 详解java中finalize的实现与相应的执行过程

    FinalReference引用 此类是一个package类型,表示它并不是公开的一部分,继承自Reference, 即表示也是一种特定的引用类型,因此每个包装在其中的对象在被回收之前,自己都会放到指定的referqyebceQueue当中. 这个引用对象专门为带finalize方法的类服务,可以理解为每一个有相应的方法的对象,其都会封装为一种finalRefernece对象. 因为finalize方法是object定义的,其默认实现为空.那么如果重写了此方法,那么方法体肯定不为空.即可以通过这

  • 详解Java中AC自动机的原理与实现

    目录 简介 工作过程 数据结构 初始化 构建字典树 构建失败指针 匹配 执行结果 简介 AC自动机是一个多模式匹配算法,在模式匹配领域被广泛应用,举一个经典的例子,违禁词查找并替换为***.AC自动机其实是Trie树和KMP 算法的结合,首先将多模式串建立一个Tire树,然后结合KMP算法前缀与后缀匹配可以减少不必要比较的思想达到高效找到字符串中出现的匹配串. 如果不知道什么是Tire树,可以先查看:详解Java中字典树(Trie树)的图解与实现 如果不知道KMP算法,可以先查看:详解Java中

  • 详解java中的static关键字

    Java中的static关键字可以用于修饰变量.方法.代码块和类,还可以与import关键字联合使用,使用的方式不同赋予了static关键字不同的作用,且在开发中使用广泛,这里做一下深入了解. 静态资源(静态变量与静态方法) 被static关键字修饰的变量和方法统一属于类的静态资源,是类实例之间共享的.被static关键字修饰的变量.方法属于类变量.类方法,可以通过[类名.变量名].[类名.方法名]直接引用,而不需要派生一个类实例出来. 静态资源分类存放的好处 JDK把不同的静态资源放在了不同的

  • 详解Java 中 RMI 的使用

    RMI 介绍 RMI (Remote Method Invocation) 模型是一种分布式对象应用,使用 RMI 技术可以使一个 JVM 中的对象,调用另一个 JVM 中的对象方法并获取调用结果.这里的另一个 JVM 可以在同一台计算机也可以是远程计算机.因此,RMI 意味着需要一个 Server 端和一个 Client 端. Server 端通常会创建一个对象,并使之可以被远程访问. 这个对象被称为远程对象.Server 端需要注册这个对象可以被 Client 远程访问. Client 端调

  • 详解Java中的ThreadLocal

    一.ThreadLocal简介 多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性.ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题. 二.ThreadLocal简单使用 下面的例子中,开启两个线程,在每个线程内部设置

  • 详解Java中@Override的作用

    详解Java中@Override的作用 @Override是伪代码,表示重写(当然不写也可以),不过写上有如下好处: 1.可以当注释用,方便阅读: 2.编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错.例如,你如果没写@Override,而你下面的方法名又写错了,这时你的编译器是可以编译通过的,因为编译器以为这个方法是你的子类中自己增加的方法. 举例:在重写父类的onCreate时,在方法前面加上@Override 系统可以帮你检查方法的正确性. @Overr

  • 详解Java中多线程异常捕获Runnable的实现

    详解Java中多线程异常捕获Runnable的实现 1.背景: Java 多线程异常不向主线程抛,自己处理,外部捕获不了异常.所以要实现主线程对子线程异常的捕获. 2.工具: 实现Runnable接口的LayerInitTask类,ThreadException类,线程安全的Vector 3.思路: 向LayerInitTask中传入Vector,记录异常情况,外部遍历,判断,抛出异常. 4.代码: package step5.exception; import java.util.Vector

  • 详解java 中Spring jsonp 跨域请求的实例

    详解java 中Spring jsonp 跨域请求的实例 jsonp介绍 JSONP(JSON with Padding)是JSON的一种"使用模式",可用于解决主流浏览器的跨域数据访问的问题.由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的<script> 元素是一个例外.利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSO

  • 详解Java 中的嵌套类与内部类

    详解Java 中的嵌套类与内部类 在Java中,可以在一个类内部定义另一个类,这种类称为嵌套类(nested class).嵌套类有两种类型:静态嵌套类和非静态嵌套类.静态嵌套类较少使用,非静态嵌套类使用较多,也就是常说的内部类.其中内部类又分为三种类型: 1.在外部类中直接定义的内部类. 2.在函数中定义的内部类. 3.匿名内部类. 对于这几种类型的访问规则, 示例程序如下: package lxg; //定义外部类 public class OuterClass { //外部类静态成员变量

随机推荐