JDK源码之Reference类分析
一 Reference抽象类
概述
在JDK1.2之前,Java中的引用的定义是十分传统的:如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。
在这种定义之下,一个对象只有被引用和没有被引用两种状态。
实际上,我们更希望存在这样的一类对象:当内存空间还足够的时候,这些对象能够保留在内存空间中;如果当内存空间在进行了垃圾收集之后还是非常紧张,则可以抛弃这些对象。
基于这种特性,可以满足很多系统的缓存功能的使用场景。
java.lang.ref包是JDK1.2引入的
引入此包的作用是对引用的概念进行了扩充,将引用分为
- 强引用(FinalReference)
- 软引用(Soft Reference)
- 弱引用(Weak Reference)
- 虚引用(Phantom Reference)
四种引用的强度按照下面的次序依次减弱:
FinalReference > SoftReference > WeakReference > PhantomReference
基于此强引用,软引用、弱引用和虚引用都是Reference抽象类的直接子类
核心源码分析
/**
* Reference就是引用类型,Java虚拟机中有三种引用类型:类类型(class type)、数组类型(array type)和接口类型,引用类型的值其实就是实例在堆内存上的地址,可以把引用近似理解为指针
* 对JVM的垃圾收集活动敏感(当然,强引用可能对垃圾收集活动是不敏感的),
* Reference的继承关系或者实现是由JDK定制,引用实例是由JVM创建,所以自行继承Reference实现自定义的引用类型是无意义的,但是可以继承已经存在的引用类型
* Reference是所有引用对象的基类。这个类定义了所有引用对象的通用操作,就像Integer类之于int类型
*/
public abstract class Reference<T> {
//Reference保存的引用指向的对象
private T referent;
/**
* 当一个Reference对象绑定的对象被GC回收时,JVM会将该引用对象被绑定到的reference对象(this)推入此队列。
* 其他程序可以通过轮询此队列,来获得该注册对象被GC的的“通知”,并完成一些工作
* 如WeakHashMap可以"知道"被GC的Entry并将其从Map中移除
* 实际只是逻辑上的一个标志,标志该对象是否加入到了队列。
* 队列里的Reference对象是通过next属性组成链式循环队列
*/
volatile ReferenceQueue<? super T> queue;
//下一个Reference实例的引用,Reference实例通过此构造单向的链表
volatile Reference next;
// 注意这个属性由transient修饰,基于状态表示不同链表中的下一个待处理的对象,主要是pending-reference列表的下一个元素,通过JVM直接调用赋值
transient private Reference<T> discovered; /* used by VM */
// 静态内部类,同步锁使用的锁对象,学习了
static private class Lock { }
private static Reference.Lock lock = new Reference.Lock();
// 获取持有的referent实例
public T get() {
return this.referent;
}
// 把持有的referent实例置为null
public void clear() {
this.referent = null;
}
// 引用对象入队
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;
}
}
二 四种引用类型
引用类型概述
Java数据类型分为两大类:
- 8种基本类型 (primitive type)
- byte
- short
- int
- long,
- float
- double
- char
- boolean
- 三种引用类型(reference):
- 类类型(class type)
- 数组类型(array type)
- 接口类型(interface type)
这些引用类型的值分别指向动态创建的类实例、数组示例和实现了某个接口的类示例或数组示例。
可见,引用类型的值其实就是实例在堆内存上的地址,可以把引用近似理解为指针。
3. JVM把引用类型分为四种类型:强引用、软引用、弱引用、虚引用
引用的类型可以描述它所指向的实例的可达性,进而供垃圾回收器根据不同类型做出不同的处理的能力,同时也提供了编程者跟踪对象生命周期的功能。
描述不同的引用类型,由Reference类的子类来实现:
- FinalReference(强引用)
- SoftReference (软引用)
- WeakReference (弱引用)
- PhantomReference (虚引用)
1. FinalReference 强引用
强引用是指创建一个对象并它赋值给一个引用,引用是存在JVM中的栈(还有方法区)中的。具有强引用的对象,垃圾回收器绝对不会去回收它,直到内存不足以分配时,抛出OOM。
大多数情况,我们new一个对象,并把它赋值给一个变量,这个变量就是强引用,源码中只有一个构造器,这里不再列举
/**
* 以下都是强引用
*/
// 方法区中的类静态属性引用的对象
private static Object finalRet2 = new Object();
// 方法区中的常量引用的对象
private static final Object finalRet3 = new Object();
public static void main(String[] args) {
// 栈上的局部变量引用的对象
Object finalRet1 = new Object();
}
2. SoftReference 软引用
软引用描述一些还有用但非必需的对象,具有软引用关联的对象,内存空间足够时,垃圾回收器不会回收它。
当内存不足时(接近OOM),垃圾回收器才会去决定是否回收它。
软引用一般用来实现简单的内存缓存
代码测试:
public class MyTest02 {
// 类对象
class UserTest {
// 模拟内存占用3M,以更好观察gc前后的内存变化
private byte[] memory = new byte[3*1024*1024];
}
/**
* 测试弱引用在内存足够时不会被GC,在内存不足时才会被GC的特性
* JVM参数 -Xms10m -Xmx10m -XX:+PrintGCDetails 将内存大小限制在20M,并打印出GC日志
*/
public void testSoftReference(){
// 创建弱引用类,将该引用绑定到弱引用对象上
SoftReference<UserTest> sortRet = new SoftReference<>(new UserTest());
// 此时并不会被GC.内存足够
System.gc();
System.out.println("GC后通过软引用重新获取了对象:" + sortRet.get());
// 模拟内存不足,即将发生OOM时,软引用会被回收,获取为null
List<UserTest> manyUsers = new ArrayList<>();
for(int i = 1; i < 100000; i++){
System.out.println("将要创建第" + i + "个对象");
manyUsers.add(new UserTest());
System.out.println("创建第" + i + "个对象后, 软引用对象:" + sortRet.get());
}
}
public static void main(String[] args) {
MyTest02 referenceTest = new MyTest02();
referenceTest.testSoftReference();
}
}
打印结果:
3. WeakReference 弱引用
弱引用描述非必需对象,但它的强度比软引用更弱一些。
WeakReference对其引用的对象并无保护作用,当垃圾回收器进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象
使用举例:
public static void main(String[] args) {
//通过WeakReference的get()方法获取弱引用对象
WeakReference<User> userWeakReference = new WeakReference<>(new User("hou"));
System.out.println("User:" + userWeakReference.get());
System.gc();
try {
//休眠一下,在运行的时候加上虚拟机参数-XX:+PrintGCDetails,输出gc信息,确定gc发生了。
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果为空,代表被回收了,如果是强引用, User user=new User("name"); 则不会回收
if (userWeakReference.get() == null) {
System.out.println("user已经被回收");
}
}
4. PhantomReference 虚引用
虚引用也被称为幽灵引用或幻引用,它是最弱的一种引用关系。
虚引用并不会影响对象的GC,而且并不可以通过PhantomReference对象取得一个引用的对象。
虚引用唯一的作用则是利用其必须和ReferenceQueue关联使用的特性,当其绑定的对象被GC回收后会被推入ReferenceQueue,外部程序可以通过对此队列轮询来获得一个通知,以完成一些目标对象被GC后的清理工作。
PhantomReference 的构造方法,与SoftReference和WeakReference不同,他的构造必须传入一个ReferenceQueue,并且get方法返回null
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}