【Java引用规范】虚引用以及引用队列

Java提供了四种引用类型:强引用、软引用、弱引用和虚引用。虚引用,作为这四种引用类型中最特殊的一种,主要用于跟踪对象被垃圾回收的状态。

名词解释:

引用实例:软引用、弱引用、虚引用等引用实例。

引用对象:被引用实例指向的对象。

虚引用(Phantom Reference)

用途

虚引用这个对象,要不是我在研究Java中的引用规范 的话,我都不知道有虚引用 。这个对象比较偏门,如果不是写框架或者一些工具类的话,是根本用不到的。

虚引用是Java中最弱的一种引用类型。虚引用主要用于检测对象是否已经没有引用关系了(可被垃圾处理器回收)。

比如说,这个特性可以用来监测对象要被回收后,做一些资源回收的动作。

在JDK9,从内部的sun工具包中抽离了一个Cleaner工具类,该类就是用于对象被回收后,释放一些相关资源,避免资源占用以及浪费。该工具类中,正是利用了虚引用的特点来自动释放资源。

与其他引用的区别

软引用、弱引用都会在垃圾回收时,对引用对象进行垃圾回收,并且无法阻止。

但是在虚引用中,垃圾回收器会先将引用实例放进引用队列 中,然后从引用队列中取出断开虚引用和引用对象的关系后才会被垃圾回收器回收。

引用类型 垃圾回收时引用对象的状态
软引用 只有在OOM抛出之前,才会回收引用对象
弱引用 每次GC时,都会回收引用对象
虚引用 不会GC,只会将引用实例放进引用队列中。需手动Clear后,引用对象才会被回收

如何创建和使用虚引用

虚引用需要与引用队列搭配使用,在虚引用中有且仅有一个构造方法:

java 复制代码
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
    super(referent, q);
}

虚引用与引用队列的关系

虚引用需搭配引用队列一起使用,在JVM中若是监测到引用对象要被回收,那么虚引用将会被添加到引用队列中。

引用队列(ReferenceQueue)

引用队列是用来配合引用工作的,不同类型的引用在对象被垃圾回收前,会有不同的行为。

引用队列的作用和特点

特点:

  1. 当引用对象已经不可达了并且虚拟机准备垃圾回收时,就会将其入队。
  2. 整个入队过程基于虚拟机且异步,无需等待。

根据引用的不同,进入引用队列后引用对象的状态也不一致

  • 软引用、弱引用:无需手动出队,会在垃圾回收时清理引用对象。
  • 虚引用 :需手动出队,然后将虚引用指向被引用对象的关系断开,说白了就是需要手动clear,不然该引用对象即使不可达了,但是虚拟机还是无法将其回收。

示例:结合引用队列监控对象生命周期

假设,你现在有一个这样的场景:

你有一个共享资源对象,会在多个线程中同时使用。但是你需要在使用完后,将占用的资源释放。

该怎么处理呢?这个时候你就不能使用完后,直接释放资源,因为有可能其他的线程正在使用或者即将使用。所以只能等该对象回收后,释放它占用的资源了。

此时,虚引用就体现出它的作用了。上面也说到了,虚引用能监听到对象的垃圾回收动作。所以我们能在准备回收时,关闭相关的资源。

java 复制代码
package com.azir.reference;

import java.io.Closeable;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class AutoCloseUtil {

    /**
     * 引用队列,用于存放被回收的虚引用
     */
    public static final ReferenceQueue<Object> REFERENCE_QUEUE = new ReferenceQueue<>();

    private static final List<PhantomImpl<? extends Closeable>> PHANTOM_LIST = new ArrayList<>();

    private static final Field REFERENT;

    /*
    * 增加自动释放资源动作
    */
    public static void addAutoClose(Object object) {
        if (object instanceof Closeable) {
            PHANTOM_LIST.add(new PhantomImpl<>((Closeable) object));
        }
    }

    static {
        try {
            REFERENT = Reference.class.getDeclaredField("referent");
            REFERENT.setAccessible(true);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }

        new Thread(() -> {
            while (true) {
                try {
                    PhantomImpl<?> phantom = (PhantomImpl<?>) REFERENCE_QUEUE.remove();
                    if (phantom == null) {
                        continue;
                    }
                    Object o = REFERENT.get(phantom);
                    ((Closeable) o).close();
                    System.out.println("资源成功关闭");
                } catch (Exception e) {
                    System.out.println("清理资源时错误");
                }
            }
        }, "auto-close-resource").start();
    }

    public static class PhantomImpl<T extends Closeable> extends PhantomReference<T> {

        public PhantomImpl(T referent) {
            super(referent, REFERENCE_QUEUE);
        }
    }
}

在上面的这个工具类中,有一个静态方法addAutoClose。该方法用于自动释放对象所占用的资源。目前该工具类仅支持继承了Closeable类的对象。

主要的逻辑是新开了一个线程,在线程中循环地从引用队列中获取虚引用,根据反射从虚引用中获取引用对象,从而调用引用对象的Close方法。

相关推荐
魔道不误砍柴功11 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_23411 分钟前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨14 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟2 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity3 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天3 小时前
java的threadlocal为何内存泄漏
java