JDK中四种对象引用类型

简介: 概述JDK1.2之前,一个对象只有“已被引用”和“未被引用”两种状态,这将无法描述某些特殊情况下的对象,比如,当内存充足时需要保留,而内存紧张时才需要被抛弃的一类对象。JDK1.2之后,Java对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱,分别介绍一下这4种引用类型。

强引用


Java中默认声明的就是强引用,代码之中普遍存在的类似Object obj=new Object()这类的引用。

只要强引用还存在, 垃圾收集器永远不会回收掉被引用的对象。哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。

如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了。

Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收
obj = null;  //手动置null
复制代码


软引用


一些还有用但并非必需的对象。

系统将要发生内存溢出异常之前, 将会把这些对象列进回收范围之中进行第二次回收。 如果这次回收还没有足够的内存, 才会抛出内存溢出异常。

这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。

在 JDK1.2 之后,提供了SoftReference类来实现软引用。

/**
 * <p>
 * Title: 创建了 10 个大小为 1M 的字节数组,并赋值给了软引用,然后循环遍历将这些对象打印出来
 * Description: 启动参数:-Xmx5M
 * </p>
 */
public class TestOom {
    private static List<Object> list = new ArrayList<>();
    public static void main(String[] args) {
        testSoftReference();
    }
    private static void testSoftReference() {
        for (int i = 0; i < 10; i++) {
            byte[] buff = new byte[1024 * 1024];
            SoftReference<byte[]> sr = new SoftReference<>(buff);
            list.add(sr);
        }
        System.gc(); //主动通知垃圾回收
        for(int i=0; i < list.size(); i++){
            Object obj = ((SoftReference) list.get(i)).get();
            System.out.println(obj);
        }
    }
}
复制代码

打印结果:

null
null
null
null
null
null
[B@cb5822
[B@4b9e13df
[B@2b98378d
[B@475530b9
复制代码

打印结果总是只有后面几个对象被保留,其他的obj全都被置空回收了。这里就说明了在内存不足的情况下,软引用将会被自动回收。

值得注意的一点 , 即使有 byte[] buff 引用指向对象, 且 buff 是一个strong reference, 但是 SoftReference sr 指向的对象仍然被回收了,这是因为Java的编译器发现了在之后的代码中, buff 已经没有被使用了, 所以自动进行了优化。


弱引用


非必需对象, 但是它的强度比软引用更弱一些。

被弱引用关联的对象只能生存到下一次垃圾收集发生之前。 当垃圾收集器工作时, 无论当前内存是否足够, 都会回收掉只被弱引用关联的对象。

在 JDK1.2 之后,提供了WeakReference类来实现弱引用。

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
 * <p>
 * Title: 创建了 10 个大小为 1M 的字节数组,并赋值给了弱引用,然后循环遍历将这些对象打印出来。
 * Description: 启动参数:-Xmx5M
 * </p>
 */
public class TestOom {
    private static List<Object> list = new ArrayList<>();
    public static void main(String[] args) {
        testWeakReference();
    }
    private static void testWeakReference() {
        for (int i = 0; i < 10; i++) {
            byte[] buff = new byte[1024 * 1024];
            WeakReference<byte[]> sr = new WeakReference<>(buff);
            list.add(sr);
        }
        System.gc(); //主动通知垃圾回收
        for(int i=0; i < list.size(); i++){
            Object obj = ((WeakReference) list.get(i)).get();
            System.out.println(obj);
        }
    }
}
复制代码

运行结果:

null
null
null
null
null
null
null
null
null
null
复制代码

可以发现所有被弱引用关联的对象都被垃圾回收了。


虚引用


最弱的一种引用关系。

一个对象是否有虚引用的存在, 完全不会对其生存时间构成影响(虚引用并不会决定对象的生命周期,有他没他都一个样),也无法通过虚引用来取得一个对象实例。

虚引用的的对象存活周期并不能确定,对象可能在任何时候被回收。

为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。 换句话说这就是起到了一个哨兵的作用。

在 JDK1.2 之后,提供了PhantomReference类来实现虚引用。

public class PhantomReference<T> extends Reference<T> {
    public T get() {
        return null;
    }
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
复制代码

通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,如上面所说,将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。


引用队列(ReferenceQueue)


引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。

与软引用、弱引用不同,虚引用必须和引用队列一起使用。

//引用队列,当引用的对象被回收后,Reference对象本身会被添加到referenceQueue中,
//相当于得到了一个通知
//软引用/弱引用中都有此构造参数,只是在虚引用中此参数变成必传了而已
ReferenceQueue<RefObj> referenceQueue = new ReferenceQueue<>();
RefObj refObj = RefObj("aaa");
PhantomReference<RefObj> ref = new PhantomReference<RefObj>(refObj,referenceQueue);
SoftReference<RefObj> ref = new SoftReference<RefObj>(refObj,referenceQueue);
WeakReference<RefObj> ref = new WeakReference<RefObj>(refObj,referenceQueue);
复制代码


总结


四种引用类型对比如下表所示:

引用类型 被垃圾回收时间 用途 生存时间
强引用 从来不会 对象的一般状态 JVM停止运行时终止
软引用 在内存不足时 对象缓存 内存不足时终止
弱引用 在垃圾回收时 对象缓存 gc运行后终止
虚引用 Unknown 标记、哨兵 Unknown


相关文章
|
4月前
|
存储 算法 Java
JDK8-JDK17中的新特性(var类型推断、模式匹配、Record、密封类)(二)
JDK8-JDK17中的新特性(var类型推断、模式匹配、Record、密封类)(二)
|
4月前
|
安全 JavaScript 前端开发
JDK8-JDK17中的新特性(var类型推断、模式匹配、Record、密封类)(一)
JDK8-JDK17中的新特性(var类型推断、模式匹配、Record、密封类)(一)
|
Java C语言
JDK的OutputStream为什么方法write(int b)的入参类型是int呢?
Java的OutPutStream中有一个方法:write(int b)  文档里是这么解释的: 将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。
1309 0
|
2天前
|
IDE Java Shell
02|手把手教你安装JDK与配置主流IDE
02|手把手教你安装JDK与配置主流IDE
6 0
|
4天前
|
Java Shell 开发者
都2024年了!你还不知道在Docker中安装jdk?
都2024年了!你还不知道在Docker中安装jdk?
|
4天前
|
存储 Cloud Native Java
Anolis安装Jdk保姆级教学
Anolis安装Jdk保姆级教学
|
4天前
|
Java 开发工具
2023全网最详细的银河麒麟操作系统,Java运行环境【jdk】安装
2023全网最详细的银河麒麟操作系统,Java运行环境【jdk】安装
|
8天前
|
Oracle Java 关系型数据库
windows 下 win11 JDK17安装与环境变量的配置(配置简单详细,包含IJ中java文件如何使用命令运行)
本文介绍了Windows 11中安装JDK 17的步骤,包括从官方网站下载JDK、配置环境变量以及验证安装是否成功。首先,下载JDK 17的安装文件,如果没有Oracle账户,可以直接解压缩文件到指定目录。接着,配置系统环境变量,新建`JAVA_HOME`变量指向JDK安装路径,并在`Path`变量中添加。然后,通过命令行(cmd)验证安装,分别输入`java -version`和`javac -version`检查版本信息。最后,作者分享了如何在任意位置运行Java代码,包括在IntelliJ IDEA(IJ)中创建的Java文件,只需去掉包声明,就可以通过命令行直接运行。
|
12天前
|
弹性计算 运维 Java
一键安装二进制JDK
【4月更文挑战第30天】
11 0
|
13天前
|
关系型数据库 MySQL 应用服务中间件
centos7在线安装jdk1.8+tomcat+mysql8+nginx+docker
现在,你已经成功在CentOS 7上安装了JDK 1.8、Tomcat、MySQL 8、Nginx和Docker。你可以根据需要配置和使用这些服务。请注意,安装和配置这些服务的详细设置取决于你的具体需求。
43 2