Java拾遗:005 - Java的四种引用类型
简介
Java中为了让程序员可以自己控制对象生命周期,提供了四种引用方式,都继承自java.lang.ref.Reference
类,它们分别是:强引用、软引用、弱引用、虚引用。
强引用(FinalReference / Finalizer)
在Java中像Object obj = new Object();
这种形式产生的引用都是强引用。强引用可以直接访问引用对象,而且强引用只在存在,那么引用对象就不会被JVM垃圾回收,即使JVM抛出OOM异常的时候,也不回收强引用引用的对象,所以不正确的使用方式可能会造成内存泄漏。
表示强引用的类java.lang.ref.FinalReference
是包级范围访问的,所以外部不能直接使用,而其子类java.lang.ref.Finalizer
同样是包级范围访问,所以一般程序员不会使用它们。
下面是一段测试代码,测试强引用对象是否会被GC掉(在VM中增加-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xmx5m参数,用于打印GC日志和设置JVM最大内存)。
// 通过new的方式生成一个强引用
Object obj = new Object();
// java.lang.Object@5702b3b1
System.out.println(obj);
// 执行GC
System.gc();
// 执行GC后,强引用不会被回收
// java.lang.Object@5702b3b1
System.out.println(obj);
// 分配一块大内存(5M),此时JVM内存耗尽,实际会抛出OOM错误,这里捕获掉,观察软引用引用的对象是否被回收
// java.lang.OutOfMemoryError: Java heap space
try {
byte[] buf = new byte[5 * 1024 * 1024];
} catch (Throwable t) {
System.out.println(t.getMessage());
}
// 即使内存溢出,也不回收强引用对象
// java.lang.Object@5702b3b1
System.out.println(obj);
软引用(SoftReference)
软引用用于描述有用但不是很重要的对象,这种对象在JVM内存充足时不会被GC掉,但在内存紧张甚至OOM时,会被GC。
软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
// 将这个对象赋值给软引用
SoftReference<Object> sr = new SoftReference<>(new Object());
// soft_ref_1 = java.lang.Object@155ec9f4
System.out.println("soft_ref_1 = " + sr.get());
// 此时对象只有一个软件引用了,执行GC
System.gc();
// 说明在内存充足的情况下,GC不会回收软引用对象
// soft_ref_2 = java.lang.Object@5a6d6fc5
System.out.println("soft_ref_2 = " + sr.get());
// 分配一块大内存(5M),此时JVM内存耗尽,实际会抛出OOM错误,这里捕获掉,观察软引用引用的对象是否被回收
// java.lang.OutOfMemoryError: Java heap space
try {
byte[] buf = new byte[5 * 1024 * 1024];
} catch (Throwable t) {
// t.printStackTrace();
}
// 说明JVM内存耗尽时会回收软引用对象
// soft_ref_3 = null
System.out.println("soft_ref_3 = " + sr.get());
弱引用(WeakReference)
弱引用是一种比软引用强度更弱的引用,功能与软引用类似,但区别在于,只要JVM发生GC,弱引用引用的对象就会被回收。
ReferenceQueue<Object> queue = new ReferenceQueue<>();
// 构造一个弱引用
// WeakReference<Object> ref = new WeakReference<>(new Object());
WeakReference<Object> ref = new WeakReference<>(new Object(), queue);
// weak_ref_1 = java.lang.Object@5702b3b1
System.out.println("weak_ref_1 = " + ref.get());
// null
System.out.println(queue.remove(200L));
// 此时对象只有一个软件引用了,执行GC
System.gc();
// 即使在内存充足的情况下,只要发生GC就会回收弱引用对象
// weak_ref_2 = null
System.out.println("weak_ref_2 = " + ref.get());
// 当垃圾对象被回收后,弱引用被加入到引用队列中
// java.lang.ref.WeakReference@22927a81
System.out.println(queue.remove(200L));
System.gc();
// null
System.out.println(queue.remove(200L));
虚引用(PhantomReference)
虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期,这种引用引用的对象随时都可能被回收,而且它的get方法永远返回null。
public class PhantomReference<T> extends Reference<T> {
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
虚引用必须和引用队列一起使用,用于跟踪垃圾回收过程。
public class PhantomReferenceTest {
static class MyType {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("--finalize--");
}
@Override
public String toString() {
return "--string--";
}
}
@Test
public void test() throws InterruptedException {
// 构造虚引用必须传入引用队列
ReferenceQueue<MyType> queue = new ReferenceQueue<>();
PhantomReference<MyType> ref = new PhantomReference<>(new MyType(), queue);
// 这种引用跟没有引用几乎没有分别,任何时候通过get方法取得的都是空值
// phantom_ref_1 = null
System.out.println("phantom_ref_1 = " + ref.get());
// 执行GC,第一次:通过观察输出日志发现,JVM找到了垃圾对象,并调用finalize方法回收内存,但没有立即加入回收队列
System.gc();
System.out.println("GC - 1");
// --finalize--
// null
System.out.println(queue.remove(200L));
// 执行GC,第二次,通过观察输出日志发现,第二次GC后JVM才真正清除垃圾对象,并将其加入引用队列(所以才能从队列中弹出值)
System.gc();
System.out.println("GC - 2");
// java.lang.ref.PhantomReference@32a1bec0
System.out.println(queue.remove(200L));
// 执行GC,第三次,后面的GC与该对象已经无关
System.gc();
System.out.println("GC - 3");
// null
System.out.println(queue.remove(200L));
}
}
结语
在一次面试中面试官问到了这个问题,当时我只知道强引用和弱引用(实际上是软引用),这种基础的问题答不上来还真是挺尴尬的。
参考资料: