我们都知道在堆里面存放着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
通过垃圾回收器对这四种引用做不同的处理,来实现对象生命周期的改变。接下来就来看一下四种引用
强引用
java
中最常用的引用类型,如Object obj = new Object()
,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。在 java.lang.ref 中并没有实际的对应类型。
强引用指向的对象任何时候都不会被回收,垃圾回收器宁愿抛出OOM
也不会对该对象进行回收
例子:
Object o = new Object();
System.out.println("before gc is "+ o);//java.lang.Object@2328c243
System.gc();
System.out.println("after gc is "+ o);//java.lang.Object@2328c243
try {
//手动调节堆内存,让程序OOM异常,看强引用回收情况 -Xms10m -Xmx10m
byte[] bytes = new byte[11 * 1024 * 1024];
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("after oom is "+ o);//java.lang.Object@2328c243
}
软引用
软引用是一种相对强引用弱化了一些的引用,对应的类为java.lang.ref.SoftReference
。
如果一个对象仅持有软引用,内存空间足够,垃圾回收器就不会回收它;但是如果内存空间不足 ,才回去回收软引用中的对象.
例子:
SoftReference
我们可以看到软引用和强引用不同的是是内存够用的时候就保留,不够用就回收。
弱引用
弱引用对应ava.lang.ref.WeakReference
类,它比软引用的生存期更短,如果一个对象仅持有弱引用,当发生垃圾回收时,不管当前内存是否足够,都会将弱引用关联的对象进行回收。
例子:
WeakReference weakRef = new WeakReference
虚引用
虚引用,顾名思义,就是形同虚设, 对应的类为java.lang.ref.PhantomReferenc
。与其他几种引用都不太一样,虚引用并不能决定对象的生命周期,也无法通过虚引用来取得一个对象实例。虚引用的主要作用是跟踪对象垃圾回收的状态。PhantomReference
的 get()
方法永远返回null
,而且只提供了与引用队列同用的构造函数。所以虚引用必须和引用队列一同使用。
例子:
ReferenceQueue referenceQueue = new ReferenceQueue();
PhantomReference
ReferenceQueue
对于软引用、弱引用和虚引用,都可以和一个引用队列ReferenceQueue
联合使用,如果软/弱/虚引用中的对象被回收,那么软/弱/虚引用就会被 JVM
加入关联的引用队列ReferenceQueue
中。 也就是说我们可以通过监控引用队列来判断Reference
引用的对象是否被回收,从而执行相应的方法。
例如下面的例子. 如果弱引用中的对象(obj
)被回收,那么软引用weakRef
就会被 JVM
加入到引用队列queue
中。 这样当我们想检测ob
j对象是否被回收了 ,就可以从 queue
中读取相应的 Reference
来判断obj
是否被回收,从而执行相应的方法。
ReferenceQueue
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 {
private T referent;
volatile ReferenceQueue super T> queue;
Reference next;
transient private Reference discovered;
static private class Lock { }
private static Lock lock = new Lock();
private static Reference 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 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 waited
return 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 {
boolean enqueue(Reference extends T> r) {
synchronized (lock) {
// 如果引用实例持有的队列为ReferenceQueue.NULL或者ReferenceQueue.ENQUEUED则入队失败返回false
ReferenceQueue> queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
// 当前引用实例已经入队,那么它本身持有的引用队列实例置为ReferenceQueue.ENQUEUED
r.queue = ENQUEUED;
// 接着,将 Reference 插入到链表
// 如果链表没有元素,则此引用实例直接作为头节点,否则把前一个引用实例作为下一个节点
r.next = (head == null) ? r : head;
// 当前实例更新为头节点,也就是每一个新入队的引用实例都是作为头节点,已有的引用实例会作为后继节点
head = r;
// 队列长度增加1
queueLength++;
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节点为自身,说明已经走过一次出队,则返回null
head = (rn == r) ? null : rn;
// Self-loop next rather than setting to null, so if a
// FinalReference it remains inactive.
// 当前头节点变更为环状队列,考虑到FinalReference尚为inactive和避免重复出队的问题
r.next = r;
// 队列长度减少1
queueLength--;
// 特殊处理FinalReference,VM进行计数
if (r instanceof FinalReference) {
VM.addFinalRefCount(-1);
}
return r;
}
return null;
}
// 队列的公有poll操作,主要是加锁后调用reallyPoll
public Reference extends T> poll() {
if (head == null)
return null;
synchronized (lock) {
return reallyPoll();
}
}
}