无论是「引用计数算法」,还是「可达性分析算法」,它们判断对象的存活都离不开「引用」
在 JDK 1.2 之前,Java 里面的引用是很传统的定义:如果 reference 类型的数据中存储的数值代表的是另外一块内存的地址,就称该 reference 数据是代表某块内存、某个对象的引用
这个定义在现在看来过于狭隘,一个对象在这种定义下只有两种状态:「被引用」or「未被引用」
我们希望对于一个对象:当内存空间还足够时,能保留在内存之中;如果内存空间在进行垃圾收集后仍然非常紧张,那就可以抛弃这个对象
基于上述所描述的应用场景,在 JDK 1.2 之后,Java 对「引用」的概念进行了扩充,分为四种:强引用、软引用、弱引用、虚引用
注意:上面提到的四种不同强度的引用依旧保持了对对象的引用,无论哪种强度的引用都和未被引用还是有区别的,未被引用的对象在 GC 时一定会被回收!!
指程序中普遍存在的引用赋值,和传统意义下的引用一致;无论任何情况下,只要强引用关系存在,垃圾收集器就永远不会回收掉被引用的对象
在 Java 程序中,最常见的引用类型就是强引用 (普通系统 99% 以上都是强引用),是最常见的普通对象引用,也是默认的引用类型
当在 Java 程序中使用new
关键字创建一个新的对象,并将其赋值给一个变量的时候,这个变量就成为指向该对象的一个强引用,如:Object obj = new Object()
强引用的对象是可触及的,垃圾收集器永远不会回收掉被引用的对象;而下面三种引用分别对应:软可触及、弱可触及、虚可触及的,在一定条件下都是可以被回收的
所以强引用是造成 Java 内存泄漏和溢出的主要原因!!
Soft reference objects, which are cleared at the discretion of the garbage collector in response to memory demand. Soft references are most often used to implement memory-sensitive caches.
指一些还有用,但非必须的对象;只要被软引用关联的对象,在系统将要发生 OOM 前,会对这些对象列进回收范围之中进行第二次回收,如果这次回收后依旧内存不足,才抛出 OOM 异常
软引用可以和一个引用队列 (ReferenceQueue) 联合使用。如果软引用所引用对象被垃圾回收,Java 虚拟机就会把这个软引用加入到与之关联的引用队列中 (弱引用和虚引用同理!)
软引用通常被用来实现内存敏感的缓存,比如:高速缓存就有使用到软引用。在内存不足的时候会清理这些缓存;在内存充足的时候保留这些缓存
再举一个更具体的例子,当使用浏览器访问一个页面,返回后再次访问相同的页面,此时是重新加载该页面,还是在第一次访问时对页面进行缓存,第二次访问直接从缓存中取呢?这涉及到系统的实现:
基于上述场景可以使用软引用,既可以保存访问过的页面内容,也可以在内存不足时回收这些内存,避免内存溢出
如果内存充足的情况下,手动通过System.gc()
通知垃圾收集器回收一次垃圾,看看软引用的对象会不会被回收!
x
public static void main(String[] args) {
// 关联的引用队列
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
// 软引用
SoftReference<Object> softReference = new SoftReference<>(new Object(), referenceQueue);
System.gc();
System.out.println(softReference.get());
System.out.println(referenceQueue.poll());
}
输出如下:
java.lang.Object
null
分析:软引用的对象并没有被回收,可以通过软引用访问到对象,也没有把软引用加入到关联的引用队列中
结论:在内存充足时,即使手动触发 Full GC,也不会回收软引用的对象!!
如果内存不足的情况下,垃圾收集器会自动触发一次 GC,看看软引用的对象会不会被回收!
x
public static void main(String[] args) {
// 关联的引用队列
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
// 软引用
SoftReference<Object> softReference = new SoftReference<>(new Object(), referenceQueue);
try {
byte[] buffer = new byte[11 * 1024 * 1024]; // 内存刚刚溢出
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(softReference.get());
System.out.println(referenceQueue.poll());
}
}
输出如下:
null
java.lang.ref.SoftReference
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.lfool.myself.Test01.main(Test01.java:18)
分析:软引用的对象被回收了,无法通过软引用访问到对象,同时把软引用加入到关联的引用队列中
结论:在内存不足时,会回收软引用的对象,同时也会把软引用加入到关联的引用队列中 (如果有关联队列的话)
指一些非必须的对象,但它比软引用强度更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生为止
public static void main(String[] args) {
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
WeakReference<Object> weakReference = new WeakReference<>(new Object(), referenceQueue);
System.out.println(weakReference.get());
System.out.println(referenceQueue.poll());
}
输出如下:
java.lang.Object
null
分析:虚引用的对象并没有被回收,可以通过虚引用访问到对象,也没有把虚引用加入到关联的引用队列中
结论:在内存充足不触发 GC 的情况下,不会回收虚引用的对象!!
public static void main(String[] args) {
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
WeakReference<Object> weakReference = new WeakReference<>(new Object(), referenceQueue);
System.gc();
System.out.println(weakReference.get());
System.out.println(referenceQueue.poll());
}
输出如下:
null
java.lang.ref.WeakReference
分析:虚引用的对象被回收了,无法通过虚引用访问到对象,同时把虚引用加入到关联的引用队列中
结论:在内存充足手动触发 GC 的情况下,依然会回收虚引用的对象!!
它是一种最弱的引用关系,虚引用是否存在完全不影响对象的生存时间,也无法通过虚引用获取一个对象的实例,它的作用只是对象在被回收时收到一个系统通知
虚引用必须和引用队列一起使用,虚引用被创建时必须提供一个引用队列作为参数,这一点和软引用和弱引用不同,它们俩是可选,而虚引用是必须!!
当垃圾收集器准备回收一个虚引用对象时,在回收前会把该虚引用加入引用队列中。在对象被回收掉了后,我们可通过引用队列获取被回收的对象引用,这就起到了一个通知的作用
x
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(new Object(), referenceQueue);
System.out.println(phantomReference.get()); // null
}
分析:上面结果证明了无法通过虚引用获取一个对象的实例
x
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(new Object(), referenceQueue);
new Thread(() -> {
while (true) {
Reference<?> poll = referenceQueue.poll();
if (poll != null) {
System.out.println("虚引用被回收了:" + poll);
}
}
}).start();
System.gc();
Thread.sleep(1000);
}
输出如下:
xxxxxxxxxx
虚引用被回收了:java.lang.ref.PhantomReference
分析:在回收对象前,把对象引用放入了引用队列中