我们都知道在堆里面存放着Java中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”。那么gc怎么判断一个对象是不是垃圾呢

判断对象是否存活有两种计数算法:引用计数法、可达性分析法

引用计数法:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一 就是如果一个对象没有被任何引用指向,则可视之为垃圾。

可达性分析法:通过一系列的称为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链Reference Chain,当一个对象到GC Root没有任何引用链相连时,则证明此对象是不可用的

这两种方式我们不做过多的介绍。但我们可以发现无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否引用链可达,判定对象是否存活都和引用离不开关系。

下来我们就开始说说引用

一、引用

要了解Reference和ReferenceQueue,我们需要先知道什么是引用。

我们用图来展示一下 Java中new一个对象 在内存中的创建过程

我们可以看出创建一个对象并用一个引用指向它的过程:

  1. 在堆内存中创建一个Student类的对象(new Student())

  2. 在栈内存中声明一个指向Student类型对象的变量(Student obj)

  3. 将栈内存中的引用变量obj指向堆内存中的对象new Student()。

这样把一个对象赋给一个变量,这个变量obj就是引用

在JDK1.2版之前,Java里面的引用是很传统的定义:

如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该reference数据是代表某块内存、某个对象的引用。

在JDK 1.2版之后,Java对引用的概念进行了扩充,将引用分为4种:

  • 强引用(Strong Reference)
  • 软引用(Soft Reference)
  • 弱引用(Weak Reference)
  • 虚引用(Phantom Reference)

Java 中引入四种引用的目的就是让程序自己决定对象的生命周期。JVM通过垃圾回收器对这四种引用做不同的处理,来实现对象生命周期的改变。关于这4种引用的介绍这里不详细展开,本文主要介绍Reference和ReferenceQueue

二、ReferenceQueue

对于软引用、弱引用和虚引用,都可以和一个引用队列ReferenceQueue 联合使用,如果软/弱/虚引用中的对象被回收,那么软/弱/虚引用就会被 JVM加入关联的引用队列ReferenceQueue中。 也就是说我们可以通过监控引用队列来判断Reference引用的对象是否被回收,从而执行相应的方法。
例如下面的例子. 如果弱引用中的对象(obj)被回收,那么软引用weakRef就会被 JVM 加入到引用队列queue 中。 这样当我们想检测obj对象是否被回收了 ,就可以从 queue中读取相应的 Reference 来判断obj是否被回收,从而执行相应的方法。

ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object obj = new Object();
WeakReference weakRef = new WeakReference<Object>(obj,queue);//置空
obj = null;
System.out.println("gc之后的值: " + weakRef.get()); // 对象依然存在//调用gc
System.gc();//如果obj被回收,则软引用会进入引用队列
Reference<?> reference = queue.remove();
if (reference != null){System.out.println("对象已被回收: "+ reference.get());  // 对象为null
}

三、Reference源码(JDK8)

要理解Reference 源码要从几个方面来看:

  1. Reference 对象的4种状态是如何转换
  2. pending-Reference list的赋值和作用
  3. Reference-handler的作用
  4. ReferenceQueue的作用

我们打开Reference的源码,可以看到最开始有一段注释说明,介绍了引用的四种状态Active ,Pending ,Enqueued ,Inactive

翻译过来大意如下:

  1. Active
    新创建的Reference实例的状态是Active。GC检测到Reference引用的实际对象的可达性发生某些改变后,它的状态将变化为Pending或Inactive。Reference注册了ReferenceQueue,则会切换为Pending,并且Reference会加入pending-Reference list中。
    Reference没有注册ReferenceQueue,会切换为Inactive。
  2. Pending
    在pending-Reference list中等着被Reference-handler 入队列queue中的元素就处于这个状态。没有注册ReferenceQueue的实例是永远不可能到达这一状态。
  3. Enqueued
    Enqueued:在ReferenceQueue队列中时Reference的状态,如果Reference从队列中移除,会进入Inactive状态
  4. Inactive
    一旦一个实例变为Inactive,则这个状态永远都不会再被改变。
    它们的关系下图很清晰的表示了出来

Reference源码中并不存在一个成员变量用于描述Reference的状态,它是通过他的成员变量的存在性"拼凑出"对应的状态。

然后我们看下Reference内部的成员变量

public abstract class Reference<T> {private T referent;    volatile ReferenceQueue<? super T> queue;Reference next;transient private Reference<T> discovered;static private class Lock { }private static Lock lock = new Lock();private static Reference<Object> pending = null;
}
  • referent:指reference引用的对象

  • queue:引用队列,Reference引用的对象被回收时,Reference实例会被放入引用队列,我们可以从ReferenceQueue得到Reference实例,执行我们自己的操作

  • next:下一个Reference实例的引用,Reference实例通过此构造单向的链表。ReferenceQueue并不是一个链表数据结构,它只持有这个链表的表头对象header,这个链表就是由next构建起来的,next也就是链表当前节点的下一个节点

  • pending:等待加入队列的引用列表,GC检测到某个引用实例指向的实际对象不可达后,会将该pending指向该引用实例。pending与discovered一起构成了一个pending单向链表,pending为链表的头节点,discovered为链表当前Reference节点指向下一个节点的引用,这个队列是由jvm的垃圾回收器构建的,当对象除了被reference引用之外没有其它强引用了,jvm的垃圾回收器就会将指向需要回收的对象的Reference都放入到这个队列里面。这个队列会由ReferenceHander线程来处理,它的任务就是将pending队列中要被回收的Reference对象移除出来,

  • discovered:pending list中下一个需要被处理的实例,在处理完当前pending之后,将discovered指向的实例赋予给pending即可。所以这个pending就相当于是一个链表。

我们来看一个弱引用的回收过程,来了解他的成员变量和四种状态的转换

 ReferenceQueue<Object> queue = new ReferenceQueue<>();WeakReference mWreference = new WeakReference(new Object(), queue); System.gc();Reference mReference = queue.remove();
  1. 创建弱引用,此时状态为Active,pending= null,discovered = null
  2. 执行GC,由于是弱引用,所以回收该object对象,将引用mWreference 放入pending队列,等待被ReferenceHandler线程处理.此时状态为PENDING,pending=mWreference,discovered
    = pending-Reference列表中的下一个元素
  3. ReferenceHandler从pending队列中取下mWreference,并且将mWreference放入到queue中,此时Reference状态为Enqueued,调用了ReferenceQueue.enqueued()后的Reference实例就会处于这个状态
  4. 当从queue里面取出该元素,则变为INACTIVE

四、ReferenceHandler

从上面的分析我们知道ReferenceHandle线程的主要功能就是把pending list中的引用实例添加到引用队列ReferenceQueue中,并将pending指向下一个引用实例。

ReferenceHandler是Reference类的一个内部类,由Reference静态代码块中建立并且运行的线程,只要Reference这个父类被初始化,该线程就会创建和运行,由于它是守护线程,除非JVM进程终结,否则它会一直在后台运行 。

private static class ReferenceHandler extends Thread {...public void run() {while (true) {tryHandlePending(true);}}...}static boolean tryHandlePending(boolean waitForNotify) {...synchronized (lock) {//如果pending队列不为空,则将第一个Reference对象取出if (pending != null) {//缓存pending队列头节点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//将头节点指向discovered,discovered为pending队列中当前节点的下一个节点,这样就把第一个头结点出队了pending = r.discovered;//将当前节点的discovered设置为null;当前节点出队,不需要组成链表了;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 waitedreturn waitForNotify;}}//将对象放入到它自己的ReferenceQueue队列里ReferenceQueue<? super Object> q = r.queue;if (q != ReferenceQueue.NULL) q.enqueue(r);return true;}

五、ReferenceQueue

  • ReferenceQueue:在Reference引用的对象被回收时,Reference对象进入到pending队列, 由ReferenceHander线程处理后,Reference就被放到ReferenceQueue里面,然后我们就可以从ReferenceQueue里拿到reference,执行我们自己的操作。这样我们只需要 ReferenceQueue就可以知道Reference持有的对象是否被回收。

  • 如果不带ReferenceQueue的话,要想知道Reference持有的对象是否被回收,就只有不断地轮训reference对象,通过判断里面的get是否为null(phantomReference对象不能这样做,其get始终返回null,因此它只有带queue的构造函数)。

  • 这两种方法均有相应的使用场景。如weakHashMap中就选择去查询queue的数据,来判定是否有对象将被回收.而ThreadLocalMap,则采用判断get()是否为null来作处理;

  • ReferenceQueue只存储了Reference链表的头节点,真正的Reference链表的所有节点是存储在Reference实例本身,Reference通过成员属性next构建单向链表,ReferenceQueue提供了对Reference链表的入队、poll、remove等操作

public class ReferenceQueue<T> {boolean enqueue(Reference<? extends T> r) {  synchronized (lock) {// 如果引用实例持有的队列为ReferenceQueue.NULL或者ReferenceQueue.ENQUEUED则入队失败返回falseReferenceQueue<?> queue = r.queue;if ((queue == NULL) || (queue == ENQUEUED)) {return false;}assert queue == this;// 当前引用实例已经入队,那么它本身持有的引用队列实例置为ReferenceQueue.ENQUEUEDr.queue = ENQUEUED;// 接着,将 Reference 插入到链表// 如果链表没有元素,则此引用实例直接作为头节点,否则把前一个引用实例作为下一个节点r.next = (head == null) ? r : head;// 当前实例更新为头节点,也就是每一个新入队的引用实例都是作为头节点,已有的引用实例会作为后继节点head = r;// 队列长度增加1queueLength++;if (r instanceof FinalReference) {sun.misc.VM.addFinalRefCount(1);}lock.notifyAll();return true;}}// 引用队列的poll操作,此方法必须在加锁情况下调用private Reference<? extends T> reallyPoll() {       /* Must hold lock */Reference<? extends T> r = head;if (r != null) {r.queue = NULL;// Update r.queue *before* removing from list, to avoid// race with concurrent enqueued checks and fast-path// poll().  Volatiles ensure ordering.@SuppressWarnings("unchecked")Reference<? extends T> rn = r.next;// Handle self-looped next as end of list designator.// 更新next节点为头节点,如果next节点为自身,说明已经走过一次出队,则返回nullhead = (rn == r) ? null : rn;// Self-loop next rather than setting to null, so if a// FinalReference it remains inactive.// 当前头节点变更为环状队列,考虑到FinalReference尚为inactive和避免重复出队的问题r.next = r;// 队列长度减少1queueLength--;// 特殊处理FinalReference,VM进行计数if (r instanceof FinalReference) {VM.addFinalRefCount(-1);}return r;}return null;}// 队列的公有poll操作,主要是加锁后调用reallyPollpublic Reference<? extends T> poll() {if (head == null)return null;synchronized (lock) {return reallyPoll();}}
}

文章转自

Reference和ReferenceQueue相关推荐

  1. Java源码:Reference与ReferenceQueue

    一:Reference与ReferenceQueue Reference的四种状态: Active:活跃,内存一开始分配的常有状态,垃圾收集器进行对该引用可达性分析后会进入Pending或Inacti ...

  2. java 源码系列 - 带你读懂 Reference 和 ReferenceQueue

    java 源码系列 - 带你读懂 Reference 和 ReferenceQueue https://blog.csdn.net/gdutxiaoxu/article/details/8073858 ...

  3. java referencequeue_java源代码 Reference和ReferenceQueue分析

    这是我个人对源码的理解,也希望大家批评指正.Reference主要是负责内存的一个状态,当然它还和Java虚拟机,垃圾回收器打交道.Reference类首先把内存分为4种状态Active,Pendin ...

  4. java referencequeue,Reference 、ReferenceQueue 详解

    ReferenceQueue 引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中 实现了一个队列的入队(enqueue)和出队(poll还有remove)操作,内部元素 ...

  5. Reference 、ReferenceQueue 详解

    2019独角兽企业重金招聘Python工程师标准>>> ReferenceQueue 引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中 实现了一个队 ...

  6. 引用解析之Reference和ReferenceQueue

    一.引用类型 Java通过引用来访问对象,从jdk1.2开始,Java将引用分为以下4种类型,强度依次减弱. (1)强引用 Strong Reference 日常开发中最经常使用的引用类型,例如Obj ...

  7. java中date类型如何赋值_一文读懂java中的Reference和引用类型

    简介 java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型.java为引用类型专门定义了一个类叫做Reference.Reference是跟jav ...

  8. 详细分析Android中的引用机制Reference(WeakReference、SoftReference、PhantomReference)

    目录 1.前言 2.四种引用 3.java.lang.ref 4.Reference 5.ReferenceQueue.enqueue(Reference) 6.ReferenceQueue.isEn ...

  9. 一文读懂java中的Reference和引用类型

    简介 java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型.java为引用类型专门定义了一个类叫做Reference.Reference是跟jav ...

最新文章

  1. large graph挖掘的技术基础
  2. Newtonsoft.Json的简单使用
  3. python缩进说法_【多选题】关于Python程序中与“缩进”有关的说法中,以下选项中错误的是()。...
  4. 【C++基础学习】关于C++静态数据成员
  5. 【报告分享】2019区块链赋能新型智慧城市白皮书.pdf(附204页电子书下载链接)
  6. 禅道 mysql 远程连接_远程访问禅道开源版数据库(基于docker)
  7. H5中 时间格式NAN-NAN-NAN
  8. mysql存储php数组_mysql数据库存储PHP数组、对象的方法
  9. Python3初级知识整理
  10. [C#] LINQ之GroupBy
  11. 通信原理基础概念概述
  12. mediasoup server 启动失败排查
  13. UnicodeEncodeError: ‘gbk‘ codec can‘t encode character ‘\u25aa‘ in position 11923: illegal multibyte
  14. csgo账号连接服务器错误,csgo连接任意官方服务器失败怎么办
  15. 十六进制表示法(二进制/十六进制/十进制之间的转换)
  16. linux常用软件收集
  17. 幼师资格证综合素质计算机知识点,教师资格证 | 综合素质基本能力13个必备知识点...
  18. 管网模型(julia)
  19. 若依框架不分离版本创建新模块(多模块版)
  20. log4j同配置下多个进程写日志

热门文章

  1. npm和node.js升级
  2. axure rp 8.0
  3. SQL基础整理(事务)
  4. 前端编码规范,个人感觉bootstrap总结的不错,拿出来给大家分享
  5. 解决redhat的未注册问题
  6. iOS发展- 文件共享(使用iTunes导入文件, 并显示现有文件)
  7. 通过一组RESTful API暴露CQRS系统功能
  8. FluentNhibernate 的数据库连接的配置
  9. 无法使用前导 .. 在顶级目录上退出
  10. Action recognition进展介绍