四种引用
-
强引用
只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收 -
软引用(SoftReference)
仅有软引用引用该对象时,在一次垃圾回收后内存仍不足时,会再次触发垃圾回收,回收软引用对象
可以配合引用队列来释放软引用自身 -
弱引用(WeakReference)
仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
可以配合引用队列来释放弱引用自身 -
虚引用(PhantomReference)
必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,
由 Reference Handler 线程调用虚引用相关方法释放直接内存
原理概述
下面的长篇大论先不看,简短的你看明白了下面的就可以不用看了:
1、Java设置了三个类:SoftReference
(软引用)、WeakReference
(弱引用)、PhantomReference
(虚引用)
2、如何使用这些对象?new SoftReference(目标对象【可选参数:,引用队列】)、new WeakReference(目标对象【可选参数:,引用队列】)、new PhantomReference(目标对象,引用队列)
3、目标对象,就是我们想要赋予特殊引用的对象,比如我想给一个Integer对象设置一个软引用,那就new SofeReference(Integer对象)
4、那么JVM在垃圾回收时,对SoftReference
(软引用)、WeakReference
(弱引用)、PhantomReference
(虚引用)的属性(前提是这个属性,只剩下这个引用,没有强引用了)特殊处理,对于它们的属性不作为强引用来处理,如果还指定了引用队列
的话,那么就会在回收掉他们的属性
后,把它们添加到引用队列
中,而我们可以创建一个线程监听引用队列
,当发现引用队列
新增了元素,那就代表有目标对象被回收了,我们做出相应操作即可。
引用原理
- 强引用:默认一个对象就是强引用
- 软引用/弱引用:创建
SoftReference
/WeakReference
对象,然后把目标对象
作为它们的属性,这样就实现了软/弱引用
,但是要注意的是,此时SoftReference
/WeakReference
对象是强引用,而它们的属性才是软/弱引用
,垃圾回收只会回收它们的属性,而它们是作为强引用对象存活下来的软/弱引用
对象如果不指定引用队列
,那么它们就只有一个地方有强引用,那就是声明他们俩的地方
,出了这个地方,他们就失去强引用了,所以即使不需要引用队列
,只要没有逃逸到方法外,软/弱引用
对象就会自然失去强引用
,然后顺其自然的被回收软/弱引用
对象如果指定引用队列
,那么它们就有两个地方有强引用,那就是声明他们俩的地方
、引用队列中会引用它们(JVM回收目标对象后,会把软/弱引用对象放到引用队列中)
,要注意去掉这两个强引用
,不然浪费空间,一般情况下都会自然而然的失去强引用
// 软引用演示
Integer i = new Integer(1); // 执行完这行代码,堆里面的new Integer(1)对象 此时具有 强引用
SoftReference<Integer> ref = new SoftReference<>(i); // 执行完这行代码,堆里面的new Integer(1)对象 此时具有 强引用、软引用 两种引用,垃圾回收无法回收堆里面的new Integer(1)对象;如果只有SoftReference<Integer> ref = new SoftReference<>(new Integer(1));,没有前面的一行,那么此时堆里面的new Integer(1)对象只有 弱引用,没有强引用,此时发生垃圾回收,堆里面的new Integer(1)对象有几率被回收
i = null; // 执行完这行代码,堆里面的new Integer(1)对象 此时只具有 弱引用,失去了强引用,如果此时发生垃圾回收,堆里面的new Integer(1)对象有几率被回收
Integer j = ref.get(); // 如果执行完这行代码 ,堆里面的new Integer(1)对象还没有被回收,那ref.get()返回的是就堆里面的new Integer(1)对象的引用,反之返回的就是null了;如果还没有被回收的话,那堆里面的new Integer(1)对象就重新获得了 强引用,此时具有 强引用、软引用 两种引用,此时发生垃圾回收,无法回收堆里面的new Integer(1)对象
// 软引用 + 引用队列 演示
// 要注意,上面的案例,SoftReference<Integer> ref,这个对象,他是强引用的,直到Integer i会被回收了,它也是是没有被回收的,那这样不就是等于是浪费了一个对象的内存空间么,没错,所以可以配合 引用对象来把这个对象也回收掉
// 引用队列
ReferenceQueue<Integer> queue = new ReferenceQueue<>();
// 关联了引用队列,当软引用所关联的 byte[] 被回收时,软引用自己会加入到 queue 中去
SoftReference<Integer> ref = new SoftReference<>(new Integer(1), queue);
System.out.println(ref.get());
// 从队列中获取无用的 软引用对象,并移除
Reference<? extends byte[]> poll = queue.poll();
while(poll != null) {
list.remove(poll);
poll = queue.poll();
}
- 虚引用:虚引用和必须配合
引用队列
使用,他跟对象回收的条件没有任何关系,他只是会在一个对象被回收以后(对象被回收后,虚引用对象会被放到指定的引用队列中),被放到在构造方法中指定好的引用队列
中,看虚引用的构造方法就可以发现,虚引用只有一个必须指定引用队列
的构造方法public PhantomReference(T referent, ReferenceQueue<? super T> q)
,而软/弱引用
有两个构造方法(目标对象)
、(目标对象,引用队列)
// 虚引用使用演示
public static void main(String[] args) {
Integer i = new Integer(1);
ReferenceQueue<Integer> queue = new ReferenceQueue<>();
PhantomReference<Integer> reference = new PhantomReference<>(i,queue);
System.out.println("i = " + i);
System.out.println("queue = " + queue);
System.out.println("reference = " + reference);
System.out.println("i = null...");
i = null;
System.out.println("gc...");
System.gc();
System.out.println("i = " + i);
System.out.println("queue = " + queue);
System.out.println("reference = " + reference);
// 这里监听线程故意写在后面,为了执行顺序看的清晰一点,其实写在哪里都是可以的
Thread thread = new Thread(() -> {
while (true) {
Reference<? extends Integer> poll = queue.poll();
if (poll != null) {
System.out.println("变量i被回收了!");
System.out.println("当前的 queue =" + queue);
System.out.println("当前的 reference =" + reference);
System.out.println("传递给我的是" + poll);
}
}
});
thread.setDaemon(true);
thread.start();
}
虚引用和对象回收条件没有任何关系
- 再次强调!!!虚引用和对象回收条件没有任何关系,你可以理解为虚引用,就是没有引用,那么这个对象就会被GC回收掉,但是被回收掉以后,虚引用对象(把被回收对象作为属性的虚引用对象)会被放到
引用队列
中,然后会有线程去做对应的操作(这个线程得我们去控制,不然我们不使用线程针对引用队列
进行操作,那么对象回收以后,也不会有任何操作,这个操作是我们去做的,JVM只负责把虚引用对象放到引用队列
,算是通知我们,这个虚引用对象的属性【我们的目标对象】被回收了,然后我们根据这个通知区做自定义的操作,我们收到通知,不做操作,也是可以的,但是不做操作,那虚引用的意义在哪呢哈哈哈)
虚引用在DirectByteBuffer
中回收直接内存的应用
DirectByteBuffer
使用的直接内存就是这样回收的,DirectByteBuffer
同时具有强引用
和虚引用
,当失去强引用
后,下次垃圾回收的时候,就会被回收掉,同时虚引用
对象就会被放到引用队列
中,然后监听引用队列
的线程,就会从引用队列
中拿出虚引用
对象,然后做释放直接内存
的操作