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


相关文章
|
1月前
|
Java Go Scala
jdk10的var局部变量类型推理
本文介绍了JDK 10中引入的局部变量类型推断功能,即`var`关键字,它允许开发者在声明具有初始值的局部变量时省略显式类型,以简化代码并提高可读性。
13 1
jdk10的var局部变量类型推理
|
3月前
|
Java 编译器 API
JDK8到JDK18版本升级的新特性问题之在JDK10中,实现局部变量类型推断,如何操作
JDK8到JDK18版本升级的新特性问题之在JDK10中,实现局部变量类型推断,如何操作
|
3月前
|
存储 Java
构造String问题之在JDK 9及更高版本中,直接访问String对象的coder和value属性,如何实现
构造String问题之在JDK 9及更高版本中,直接访问String对象的coder和value属性,如何实现
|
5月前
|
存储 Java 关系型数据库
数据类型的取值范围以及Java和Mysql数据库的类型对照--强调时间类型的转换(jdk1.8)
数据类型的取值范围以及Java和Mysql数据库的类型对照--强调时间类型的转换(jdk1.8)
46 0
|
5月前
|
Java
JavaSE——JDK8新特性(2/2):方法引用(静态方法的引用、实例方法的引用、特定类型的方法引用、构造器引用)
JavaSE——JDK8新特性(2/2):方法引用(静态方法的引用、实例方法的引用、特定类型的方法引用、构造器引用)
35 0
|
6月前
|
Java 开发者
JDK 21中的记录模式(Record Patterns):简化对象匹配与解构
本文将详细介绍JDK 21中引入的新特性——记录模式(Record Patterns)。记录模式是一种强大的语言特性,它允许开发者在switch表达式中使用简化的语法来匹配和解构记录类型(record types)。本文将解释记录模式的概念、语法、使用场景以及与传统模式匹配的区别,并通过示例代码展示记录模式在实际开发中的应用。
|
6月前
|
存储 算法 Java
JDK8-JDK17中的新特性(var类型推断、模式匹配、Record、密封类)(二)
JDK8-JDK17中的新特性(var类型推断、模式匹配、Record、密封类)(二)
|
6月前
|
安全 JavaScript 前端开发
JDK8-JDK17中的新特性(var类型推断、模式匹配、Record、密封类)(一)
JDK8-JDK17中的新特性(var类型推断、模式匹配、Record、密封类)(一)
|
6月前
|
存储 前端开发 Java
Java【代码分享 13】前端动态添加一条记后端使用JDK1.8实现map对象根据key的部分值进行分组(将map对象封装成指定entity对象)
Java【代码分享 13】前端动态添加一条记后端使用JDK1.8实现map对象根据key的部分值进行分组(将map对象封装成指定entity对象)
49 0
|
IDE 安全 Java
Java 版本、语言规范、API、JDK、IDE、Java 源程序编译、执行原理(跨平台性根本原因)、特殊字符用法、8 大数据类型小结
Java 版本、语言规范、API、JDK、IDE、Java 源程序编译、执行原理(跨平台性根本原因)、特殊字符用法、8 大数据类型小结
210 0
Java 版本、语言规范、API、JDK、IDE、Java 源程序编译、执行原理(跨平台性根本原因)、特殊字符用法、8 大数据类型小结