1、前言
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判断对象是否存活,都和“引用”离不开关系。
在 JDK1.2 以前, Java 里面的引用是很传统的定义:如果 reference 类型的数据中存储的数值代表的是另一块内存的起始地址,就称该 reference 数据是代表某块内存、某个对象的引用。
一个对象在上面的定义下,只有“被引用”和“不被引用”两种状态,对于描述一些“食之无味、弃之可惜”的对象就显得无能为力。比如说,有一些对象,当内存空间充足的时候,它最好保留在内存里,当内存不足的时候,抛弃他们也没多大关系。其实说的就是缓存。
在 JDK1.2 之后,Java 对引用的概念进行了扩充,把引用分为:强引用、软引用、弱引用和虚引用。
然而,我们开发中看到的基本上 99% 都是强引用,其他三个都是在很特殊的场景下才会用。但是!面试必问。
我们可以在java.lang.ref
包下找到类Reference
。
2、强引用(Strong Reference)
在 Java 程序中,最常见的引用类型就是强引用,也是默认的引用类型。
当 Java 语言中使用 new 操作符创建一个新的对象,并将其赋值给一个变量的时候,这个变量就称为指向该对象的一个强引用。
强引用的对象是可触及的,垃圾收集器永远不会回收掉被引用的对象。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显示地将引用赋值为 null,就是可以当做垃圾被收集了,当然具体回收实际还是看垃圾收集策略。
相对的,软引用、弱引用和虚引用的对象是软可触及、弱可触及和虚可触及的,在一定条件下,都是可以被回收的。所以,强引用是造成 Java 内存泄漏的主要原因之一。
比如一个例子:
StringBuffer str = new StringBuffer("学哥斌");
局部变量 str 指向 StringBuffer 实例所在的堆空间,通过 str 可以操作该实例,那么 str 就是 StringBuilder 实例的强引用。
此时,再运行一个赋值语句:
StringBuffer str1 = str;
就会变成如下:
str 和 str1 都是强引用,可以通过该引用访问目标对象,所引用的对象在任何时候都不会被系统回收,虚拟机宁愿 OOM 也不会回收它。
3、软引用(Soft Reference)
软引用是用来描述一些还有用,但是非必要的对象。只被软引用关联着的对象,在系统将要发生内存溢出的异常前,会把对象列进回收范围之中进行第二次回收,即:先回收没有引用的对象,回收后如果空间够了,软引用就不被回收;如果第一次回收掉没引用的对象后,空间还是不足,那么就会回收软引用的对象;如果软引用回收了还是空间不足,就报 OOM。
软引用通常用来实现内存敏感的缓存。比如:高速缓存就有用到软引用。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
垃圾回收器在某个时刻决定回收软可达的对象的时候,会清理软引用,并可选地把引用存放到一个引用队列(Reference Queue)。
类似弱引用,只不过 Java 虚拟机会尽力让软引用的存活时间长一点,迫不得已才清理。
在 JDK1.2 之后提供了java.lang.ref.SoftReference
类来实现软引用。
Object obj = new Object(); // 生命强引用
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; // 销毁强引用
经过上面三步后,第一行 new 出来的对象就是弱引用对象,当内存不足时,是会被清理掉的。
4、弱引用(Weak Reference)
弱引用也是用来描述非必需对象,被弱引用关联的对象一旦遇到 GC,无论内存是否足够都要被回收。
由于垃圾回收器的线程通常优先级很低,因此,并不一定能很快发现持有弱引用的对象。在这种情况下,弱引用对象可以存在较长的时间。
弱引用和软引用一样,在构造弱引用时,也可以制定一个引用队列,当弱引用对象被回收时,就会假如指定的引用队列,通过这个队列可以跟踪对象的回收情况。
软引用、弱引用都非常适合来保存那些可有可无的缓存数据,如果这么做,当系统内存不足时,这些缓存数据就会被回收,不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用。
在 JDK1.2 以后提供了java.lang.ref.WeakReference
类来实现弱引用。
Object obj = new Object(); // 声明强引用
WeakReference<Object> wr = new WeakReference<Object>(obj);
obj = null; // 销毁强引用
弱引用对象与软引用对象的最大不同就在于,当 GC 在进行回收时,需要通过算法检查是否回收软引用对象,而对于弱引用对象,GC 总是进行回收。弱引用对象更容易、更快被 GC 回收。
比如java.util.WeakHashMap
,它的底层就是使用了java.lang.ref.WeakReference
。
被这个WeakHashMap
引用的对象很适合作为缓存,当系统执行 GC 就被回收了。
5、虚引用(Phantom Reference)
虚引用是引用类型最弱的一个,弱到发虚,为什么说它虚呢,是因为想通过它来访问对象都做不到,跟没有引用一样,所以说它“虚”。
一个对象是否有虚引用,完全不会决定对象的生命周期。如果一个对象仅持有虚引用,那它很没有引用几乎是一样的,随时被垃圾回收器回收。
虚引用不能单独使用,也不能用来访问对象,当试图使用虚引用的get()
方法来取得对象时,总是 null。
那虚引用有啥用呢?
为一个对象设置虚引用关联的唯一目的在于跟踪垃圾回收过程。比如:能在这个对象被收集器回收时收到一个系统通知。
虚引用必须和引用队列一起使用,这一点可以通过它的构造函数了解到:
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入到引用队列,已通知应用程序对象的回收情况。
由于虚引用可以跟踪对象的回收时间,因此,也可以将一些资源释放操作放置在虚引用中执行和记录。
Object obj = new Object(); // 声明强引用
ReferenceQueue phantomQueue = new ReferenceQueue();
PhantomReference<Object> pf = new PhantomReference<Object>(obk, phantomQueue);
obj = null; // 去除强引用