Reference源码分析
本文基于JDK1.8.0_271分析,native源码下载自openJDK官网(build 1.8.0_41-b04)
0. 前言
JDK1.2开始,引入了一个新的包,java.lang.ref
:
- java.lang.ref
- Finalizer.class
- FinalizerHistogram.class
- FinalReference.class
- PhantomReference.class
- Reference.class
- ReferenceQueue.class
- SoftReference.classs
- WeakReference.class
- 额外的还有一个在sun.misc包下
- Cleaner.class
随之带来了四个新的概念:
- 强引用Storn References:随处可见,我们直接new出来的代码就是强引用。内存不足时,宁愿抛出
OutOfMemoryError
也不愿意回收这些对象。我们可以手动的设置为null让GC回收他。 - 软引用SoftReference:等级比强引用低,只有在内存不足的时候才回去回收。我们可以实现内存敏感的高速缓存。
- 弱引用WeakReference:等级比软引用低,不管内存足不足,发生GC时,就有可能被回收。如果这个对象是一个偶尔使用的对象,并且需要在使用的时候就能获取到,但又不想影响生命周期,就可以使用WeakReference,比如处理内部类内存泄漏的问题时。
- 虚引用PhantomReference:等级比弱引用低,形同虚设,任何时候都可能被回收。它可以和引用队列配合用于监控对象是否被回收
并且,除了强引用之外,其他三个都可以和引用队列配合使用,就是在调用他们的构造方法时,除了传入Object外,还可以传入一个ReferenceQueue,他的作用就是当Object被回收时,JVM会自动帮我们把这个软/弱/虚引用添加到这个Queue中。通过这个功能可以监控对象是否被回收。
直到JDK8为止,只存在四种引用,这些引用是由JVM创建,因此直接继承java.lang.ref.Reference
创建自定义的引用类型是无效的,但是可以直接继承已经存在的引用类型,如sun.misc.Cleaner
就是继承自java.lang.ref.PhantomReference
。
接下来我们就可以源码,但是在此之前,我们得带着问题看:
- 软引用是内存不足才会回收,那么什么叫内存不足?
- 弱引用只要发生GC时就回收,但是他不是引用着对应的强引用,那么他为啥能被回收?
- 虚引用形同虚设,任何时候都会被回收,那么到底什么时候会被回收?
- 当引用对象被回收时,他们是怎么被添加到若引用队列的?
1. Reference.java
1.1 源码
首先来看下他的构造方法和成员变量:
|
|
1.1.1 构造方法
构造方法有两种,一种只传入referent,另一种还要传入一个Queue,如果传入的queue为null的话,成员变量queue就等于ReferenceQueue.NULL
。
1.1.2 成员变量
reference:代表我们传入的对象。
queue: ReferenceQueue,就是引用队列。
1
volatile ReferenceQueue<? super T> queue;
next:下一个Reference实例的引用,主要是在ReferenceQueue中使用。
1 2 3 4 5 6 7
/* When active: NULL * pending: this * Enqueued: next reference in queue (or this if last) * Inactive: this */ @SuppressWarnings("rawtypes") volatile Reference next;
discovered:注意这个属性由transient修饰,基于状态表示不同链表中的下一个待处理的对象,主要是PendingList的下一个元素,通过JVM直接调用赋值。
1 2 3 4 5
/* When active: next element in a discovered reference list maintained by GC (or this if last) * pending: next element in the pending list (or null if last) * otherwise: NULL */ private transient Reference<T> discovered;
lock: 线程安全的锁
1 2 3 4 5 6 7
/* Object used to synchronize with the garbage collector. The collector * must acquire this lock at the beginning of each collection cycle. It is * therefore critical that any code holding this lock complete as quickly * as possible, allocate no new objects, and avoid calling user code. */ static private class Lock { } private static Lock lock = new Lock();
pending:等待加入queue的Reference对象,在GC时由JVM设置,会有一个java层的线程(ReferenceHandler)源源不断从PendingList中取出元素加入到queue中
1 2 3 4 5 6
/* 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;
1.1.3 方法
|
|
1.2 ReferenceHandler线程
|
|
源码很简单,这个线程的主要作用就是不断的去调用tryHandlePending()
方法
我们再看看他的启动:
|
|
1.3 核心方法:tryHandlePending
|
|
源码很好理解,就是线程死循环源源不断的从PendingList中取出pending,然后将其加入到ReferenceQueue中去,而PendingList又是根据discovered来移动的。
并且对于Cleaner类型有特别处理,当其指向的对象被回收时,会调用clean进行资源回收。
那么,我们知道了对pending的处理,那么,那些对象什么时候会被加入到PendingList中去呢?根据注释可以得知,我们需要从native层去找到答案。
2. referenceProcessor.cpp
Reference对应的native代码主要在referenceProcessor.cpp文件中,这个文件在hotspot\src\share\vm\memory
路径下。
加入PendingList的核心方法是process_discovered_references()
:
|
|
从上面注释,我们可以看到大致的处理过程是:
- 处理软引用
- 处理弱引用
- 处理需引用
但是他们都调用了process_discovered_reflist这个方法,唯一的区别只是传入的refs_list不同。
2.1 process_discovered_reflist()
|
|
2.2 process_phase1()
|
|
这段代码很好理解,遍历refs_list,取出里面的每一个元素,并判断是否应该移除,判断的依据包括这个元素所引用的对象是否存活以及ReferencePolicy。那么这个ReferencePolicy是啥?
2.2.1 referencePolicy.hpp
打开这个文件,我们可以看到ReferencePolicy的定义,除了这个类以外,还有4个他的子类:
- NeverClearPolicy
- AlwaysClearPolicy
- LRUCurrentHeapPolicy
- LRUMaxHeapPolicy
对于前两个类的should_clear_reference()方法很简单,一个永远返回false,一个永远返回true。那下面两个是啥?
referencePolicy.cpp
截取下主要代码:
|
|
单纯看他两的setup方法:
|
|
前者计算的是上次GC后的可用堆大小,后者计算的是(堆大小-上次GC时使用的大小)
而should_clear_reference方法:
|
|
但是,timestamp_clock
和 java_lang_ref_SoftReference::timestamp(p)
是什么呢?
第一个不说,第二个看这个形式,是不是很熟悉,这不就是jni嘛,我们再次回到java层。
软引用、弱引用和虚引用
Reference主要的三个子类,弱引用和虚引用虽然继承自Reference,但是他们改动不大:
|
|
反倒是弱引用,改动了较多:
|
|
clock
就是上面native代码的timestamp_clock
,timestamp
就是java_lang_ref_SoftReference::timestamp(p)
。如果上次GC时有调用过get()
那么interval为0,否则就是他们之间的差值。也就是说,如果GC间隔时间太长了,就回被回收。
所以我们更新下,判断软引用是否改被移除的依据包括这个元素所引用的对象是否存活、Policy策略以及存活时间。
2.3 process_phase2()
|
|
这个代码很简单,就是遍历refs_list,如果所指向的对象还存活,则从list中移除,否则保留着继续遍历。
2.4 process_phase3()
|
|
3. 总结
看完了代码,我们来回答下之前提出的问题:
软引用是内存不足才会回收,那么什么叫内存不足?
根据
process_phase1()
中ReferencePolicy的四个子类,我们可以得知,内存不足的定义和该引用对象get的时间以及当前堆可用内存大小都有关系。具体可以看LRUCurrentHeapPolicy和LRUMaxHeapPolicy弱引用只要发生GC时就回收,但是他不是引用着对应的强引用,那么他为啥能被回收?
这个问题可以回顾
process_phase3()
,它里面会有一个if判断,如果clear_referent
为true,就回收,并把reference
置为null,否则不回收。那么这个变量是在哪被赋值的呢?我们一个个调用看上去,就会神奇的发现,在process_discovered_references()
方法中,软引用和弱引用的clear_referent
直接为true,虚引用为false。所以只要发生了GC,弱引用就会被回收。(而软引用如果内存充足的情况下,在process_phase1()
时就已经被从队列中移除了,并不会走到process_phase2()
)虚引用形同虚设,任何时候都会被回收,那么到底什么时候会被回收?
虚引用是会影响对象生命周期的,如果不做任何处理,只要虚引用不被回收,那其引用的对象永远不会被回收。所以一般来说,从ReferenceQueue中获得PhantomReference对象后,如果PhantomReference对象不会被回收的话(比如被其他GC ROOT可达的对象引用),需要调用
clear
方法解除PhantomReference和其引用对象的引用关系。当引用对象被回收时,他们是怎么被添加到若引用队列的?
看
tryHandlePending()
。