虚引用对象到底什么时候被回收?
晚上被这个问题干了一个多小时。。。
问题来源代码:
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
/**
* @Author: xiaoshijiu
* @Date: 2020/2/27
* @Description:
*/
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
// 虚引用必须要和引用队列一起使用,他的get方法永远返回null
PhantomReference<byte[]> phantomReference = new PhantomReference<>(
new byte[1024 * 1024 * 5], queue);
System.out.println(queue.poll());
System.gc();
Thread.sleep(300L);
System.out.println(queue.poll());
byte[] bytes = new byte[1024 * 1024 * 6];
}
}
jvm设置了参数-Xmx10m -Xms10m,最大内存10m
运行结果一直是:
报OOM,表明不足6m去创建新的byte数组,也即前面的5m虚引用指向的byte数组没有被垃圾回收;
我就很郁闷了,进行了一次GC,虚引用也被添加到了与其关联的引用队列中,但是为什么虚引用所指向的对象,就一直没有被回收呢?
这一点跟 软引用和弱引用 真的不同啊
将上面代码分别换成软引用和弱引用,尝试:
import java.lang.ref.SoftReference;
/**
* @Author: xiaoshijiu
* @Date: 2020/2/27
* @Description:
*/
public class SoftReferenceDemo {
public static void main(String[] args) throws InterruptedException {
// 软引用测试
SoftReference<byte[]> phantomReference = new SoftReference<>(
new byte[1024 * 1024 * 5]);
byte[] bytes = new byte[1024 * 1024 * 6];
}
}
import java.lang.ref.WeakReference;
/**
* @Author: xiaoshijiu
* @Date: 2020/2/27
* @Description:
*/
public class WeakReferenceDemo {
public static void main(String[] args) throws InterruptedException {
// 弱引用测试
WeakReference<byte[]> phantomReference = new WeakReference<>(
new byte[1024 * 1024 * 5]);
System.gc();
byte[] bytes = new byte[1024 * 1024 * 6];
}
}
同样的设置jvm最大的10m内存
他们两个的运行结果一致,在创建第二个大对象的时候,内存不足,GC则会回收之前的5m对象;
在网上找了很多答案,都没有介绍到虚引用指向的对象具体被回收的时间;
后来看了仔细研读了JDK8中对PhantomReference类的介绍,才逐渐看明白;
在收集者确定其指示物可能被回收之后排入队列的Phantom参考对象。 幻像引用最常用于以比Java完成机制可能更灵活的方式安排事先清理操作。
如果垃圾收集器在某个时间点确定幻像引用的引用是phantom reachable ,那么在那个时间或稍后的时间,它将引入引用。为了确保可回收对象保持原样,可能无法检索幻像引用的引用:虚幻引用的get方法始终返回null 。
与软弱引用不同,幻像引用在垃圾收集器排入队列时不会自动清除。 通过幻影引用可访问的对象将一直保持到所有这样的引用被清除或者自身变得不可访问。
仔细读最后一句话,明确指出了他跟软弱引用不同,即使入队列了GC也不会自动清除,直到 所有这样的引用被清除或者自身变得不可访问
有点不好理解,毕竟是英文翻译过来的。
结合代码尝试:
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
/**
* @Author: xiaoshijiu
* @Date: 2020/2/27
* @Description:
*/
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
// 虚引用必须要和引用队列一起使用,他的get方法永远返回null
PhantomReference<byte[]> phantomReference = new PhantomReference<>(
new byte[1024 * 1024 * 5], queue);
System.out.println(queue.poll());
System.gc();
Thread.sleep(300L);
// 根据JDK8的api文档介绍,将所有这样的引用被清除或者自身变得不可访问,GC才会回收
Reference<? extends byte[]> poll = queue.poll();
System.out.println(poll);
poll = null;
phantomReference = null;
byte[] bytes = new byte[1024 * 1024 * 6];
}
}
加了几行代码,将原虚引用和出队的虚引用都置为null
最后成功:再次创建大对象,没有报OOM
这样我们就可以重新总结虚引用:GC时会入队列,但是只有在所有这样的引用被清除或者自身变得不可访问才会回收所指向的对象。
感悟
这么细的知识,网上很难找的想要的答案,以后在遇到还是要好好读读JDK官方文档的解析。