强引用,弱引用,软引用,虚引用它们有什么区别?你知道吗?
发布时间:2026/8/14 5:34:09 作者:尧图编辑部 阅读量:1,286

目录一. 了解JVM内存模型二. 强引用类型解析2.1 强引用理论解释2.2 强引用代码演示2.3 强引用的使用场景三. 软引用类型解析3.1 软引用理论解释3.2 软引用与强引用的区别3.3 软引用代码展示3.4 软引用的使用场景四. 弱引用类型解析4.1 弱引用理论解释4.2 弱引用代码演示4.3 弱引用的使用场景五. 虚引用类型解析5.1 虚引用构造器展示5.2 虚引用与弱引用的比较5.2 虚引用的使用场景六. 总结一. 了解JVM内存模型(注如果已经了解JVM内存模型的同学可以直接看下面)在讲解强弱软虚引用四种引用之前我们先来回顾一下 JVM 虚拟机的内存模型简单了解一下对象在 JVM 中的存放原理也是为了让不了解Java虚拟机的同学在看此篇文章的时候不那么迷惑下面开始正题。Java 虚拟机运行在内存中当虚拟机拿到了自己可支配的内存之后会将内存分为五个部分分别是 栈(JVM栈)堆方法区(JDK1.8之后改名元空间)程序计数器本地方法栈。如下图所示JVM栈运行我们的程序如 main 方法堆存放对象创建的对象都存放在堆中方法区(元空间)存放类加载器静态变量静态方法全部变量这里举个例子如下代码所示我定义一个 main 函数打印一句话那么该程序就会开辟一个新的JVM栈当我们再定义另一个 main 方法时就会再开辟一个新的JVM栈JVM栈是每个线程私有的但它们会共享堆中的对象和元空间中的全局静态变量。二. 强引用类型解析2.1 强引用理论解释Java 中我们通常都是通过 Object o new Object() 的方式来创建一个对象这个 new Object() 对象就存放在堆中我们的 o 就存放在各自程序的JVM栈中是私有的o 中保存了堆中对象的内存地址如下图所示当我们的程序想要操作对象的时候就会通过 o 中保存的内存地址去堆中寻找该对象然后对该对象进行操作。强引用对自然非常强只要堆中的对象有变量指向它它就不会被GC回收只有没有人任何对象引用它的时候它才会被GC垃圾回收器回收。2.2 强引用代码演示如下代码展示。写了注释所以就不再赘述了public class Test { // 重写 Test 类中的 finalize() 方法 Override public void finalize() throws Throwable{ // 打印一句话作为标记证明该方法被调用过 System.out.println(finalize方法执行); } public static void main(String[] args) throws Exception { // 创建类对象 t Test t new Test(); System.out.println(t第一次获取对象); t null; // 开启垃圾回收GC System.gc(); // 因为GC垃圾回收是另外的垃圾回收线程所以我们让主线程先睡两秒避免造成误差 Thread.sleep(2000); // 经过GC之后再次获取t对象 System.out.println(t第二次获取对象); } }关于 finalize 方法我还是要说明一下。finalize 方法是定义在 Object 类中的方法每个对象都可以调用该方法当对象被回收的时候就会执行 finalize 方法因为Java虚拟机的GC垃圾回收是在一定条件下才运行的我们这里只把 t 对象赋值为 null 不一定会启动 GC垃圾回收过程所以我们通过 System.gc() 主动启动垃圾回收线程我也在注释中也说明了GC垃圾回收有它自己的线程所以我们调用 sleep 方法让 main 方法的进行先睡一会让GC完成之后再去重新获取。运行之后如下图所示在控制台中我们也可以看到finalize 方法被执行打印出来了说明对象被回收之后会执行自身的 finalize 方法。2.3 强引用的使用场景强引用的使用场景是我们在 Java 开发的时候最为常用的正常的 new 对象的方式产生的对象引用方式都是强引用它最大的特点就是只要有强引用指着垃圾回收器就不能回收哪怕产生OOM内存溢出都不会回收。三. 软引用类型解析3.1 软引用理论解释首先我需要给大家明确一点软引用本身其实是一个类名为 SoftReference可以添加泛型。我们创建出该类的对象那么该类的对象引用类型就是软引用。创建软引用对象的方式为 SoftReference? m new SoftReference?(new )软引用内存图如下所示m 对象在JVM程序栈中软引用对象和软引用对象内部的数组对象都包含在堆中m 对象与软引用对象之间是正常的强引用软引用对象与内部的数组对象它们两个之间则是弱引用在图中也表示为虚线没有强引用那么强。3.2 软引用与强引用的区别刚才说到了软引用没有强引用强是如何体现的呢假设我们 Java 虚拟机的堆内存为20M现在我定义了一个软引用的字节数组大小为10M接下来我还要创建一个大小为12M的数组此时我们来看10M12M20M已经要内存溢出所以按道理来说我们想要创建的数组是不可能创建成功的但是由于我们先前创建的字节数组为软引用那么此时堆就会把这个10M的字节数组从堆中清除清除之后就有足够的内存空间容纳新创建的数组了而假设我们要创建一个大小为5M的数组此时 10520还没有超过内存大小不会内存溢出那么此时堆就不会把这个10M的字节数组清除而是让它继续留在堆中。3.3 软引用代码展示如下展示软引用对象被清除的代码注释我都写的很清楚应该不需要做过多的解释说明了。public static void main(String[] args) throws InterruptedException { // 创建一个软引用对象 m并在m软引用对象在定义一个 10M 的字节数组 SoftReferencebyte[] m new SoftReference(new byte[1024 * 1024 * 10]); // 通过get方法获取一下软引用对象中的字节数组对象 System.out.println(m.get()---第一次获取软引用字节数组对象); // 调用 GC启动垃圾回收 System.gc(); // 让 main 线程睡 0.5秒 Thread.sleep(500); // GC之后再获取软引用的字节数组对象看是否能获取到 System.out.println(m.get()---GC之后第二次获取软引用字节数组对象); // 再创建一个大小为 12M 的字节数组 byte[] b new byte[1024 * 1024 * 12]; // 获取新创建的数组看是否被创建成功看能否获取成功 System.out.println(b---获取新创建的字节数组对象b); // 此时再次获取先前的软引用对象 m看是否还存在 System.out.println(m.get()---创建字节数组b之后再来获取软引用字节数组对象); }在运行 main 函数之前我们需要先对JVM栈做一个配置如下图点击该类配置项点击之后弹出如下界面这里我们配置一下 VM 虚拟机的参数项这里配置一下JVM内存大小为20M每个人的程序由于你可能在当前模块定义了其他的类或者程序或者堆中已经存放了其他对象导致结果可能不太相同是正常现象可以试着变换一下内存参数大小值或数组大小这里我就是将内存设置为了20M在调试运行程序之前也试过其他数值大小但没有得出正确的结果所以结果不一样你可以改变一下大小多试几次换个数值试一下即可不一定是程序的问题。只要能让内存溢出的情况产生即可。配置完成后如下所示点击应用确认然后关闭窗口即可运行 main 函数然后我们运行上述 main 方法在控制台中得到如下结果可以看到在创建强引用硬数组之后我们并没有手动将弱引用的数组对象赋值为 null但是在第三次获取软引用字节数组的时候它却变成了 null这就是弱引用的特性内存足够时允许它留在堆中对内存不够用的时候就把它从堆内存中清除测试过软引用对象的字节数组被清除的情况之后我们再来测试一下不被清除的情况很简单把要创建强引用的数组大小变小一点即可这里我改为5M即[1024 * 1024 * 5]其他代码都不用动重新运行 main 函数在控制台得到了不一样的结果如下所示可以看到此时创建了一个小的数组没有内存溢出的情况下第三次获取软引用的数组时可以获取得到3.4 软引用的使用场景通过上面弱引用的特性我们其实可以大概能了解它的一个使用场景有什么东西我们可以满足内存足够时让它存在内存不足时让它离开呢当然是缓存啦各位同学想一下如果一张图片非常的大或者其他资源加载需要时间比较久我们就可以把它定义为弱引用提前加载到内存中在需要的时候直接访问当内存不够的时候再把它从内存中清除需要的时候在读取到内存中。这就是软引用的其中一个使用场景。四. 弱引用类型解析4.1 弱引用理论解释弱引用与软引用类似都是一个了类Java中叫 WeakReference该类创建的对象的引用方式便是弱引用创建对象的方法也是与软引用相同这里就不举了了下面演示代码的时候也可以看到。弱引用与强引用在遇到GC垃圾回收时的情况恰恰相反强引用的对象不管哪次GC垃圾回收时都不会被清除而弱引用对象只要遇到GC都会被回收4.2 弱引用代码演示代码如下所示注释都已经说明不需要做过多解释public class WeakReferenceTest { public static void main(String[] args) throws InterruptedException { // 通过弱引用创建一个字符串对象 WeakReferenceString m new WeakReference(new String(我是弱引用)); // 打印弱引用对象的字符串对象 System.out.println(m.get()---GC之前第一次获取弱引用字符串对象); // 手动开启GC System.gc(); // 让 main 线程睡1秒防止GC未结束main函数先运行完导致结果异常 Thread.sleep(1000); // GC之后再来获取弱引用对象看能否获取到 System.out.println(m.get()---GC之后第二次获取弱引用字符串组对象); } }然后我们运行上述 main 方法在控制台中得到如下结果4.3 弱引用的使用场景弱引最重要的一点应用就是在 ThreadLocal 中使用弱引用避免了 ThreadLocal 造成内存泄露问题这里不做大篇幅的讲解想了解的可以看我的另一篇文章。深挖 ThreadLocal 底层原理它有什么用学会之后手撕面试官_程序猿ZhangSir的博客-CSDN博客五. 虚引用类型解析5.1 虚引用构造器展示虚引用同样也是一个类它的构造方法需要我们传递两个参数如下图所示第一个参数是虚引用对象第二个参数需要我们传递一个队列。5.2 虚引用与弱引用的比较虚引用与弱引用相似的是被虚引用指向的对象仍旧会被GC垃圾回收器回收。虚引用与弱引用不同的是虚引用内部的对象我们永远无法通过 get() 方法获取到其中的值上面我们可以看到强软弱三种引用都可以获取到其对象虚引用则不可以。虚引用在代码中使用很少这里就不作代码演示了面试中问的也不是特别多重点记住上面三种引用。5.2 虚引用的使用场景那么既然获取不到虚引用的对象要它有什么用呢虚引用最主要的一个场景就是管理堆外内存。如下图所示Java 虚拟机是运行在操作系统的内存之中的当我们要处理一份数据的时候第一步操作系统先读取到自己的内存中第二步然后再拷贝到我们的JVM内存中第三步JVM处理完毕之后再拷贝回操作系统内存中第四步再由操作系统返回至外界中间经过了操作系统这一媒介效率可想而知是比较低的所以 Java 就提供了虚引用它可以直接得到并直接操作操作系统的内存提高了效率。但是随着虚引用可以管理堆外内存一个新的问题产生。我的虚引用对象存放在JVM的堆中虚引用所指向的对象则是在操作系统的内存中。假如说我的虚引用在JVM内存中已经赋值为空虚引用对象与操作系统中的对象断开了联系。虚引用之前所指向的操作系统内存中的对象是不能被JVM垃圾回收器回收的因为虚引用指向的对象并没有存放在JVM内存的堆中而是直接存放在操作系统的内存中所以JVM是无法将其回收的一旦长时间如此操作系统的内存终究会出现大量没有引用的对象而且无法被清除造成内存泄露。为了避免内存泄漏相框的发生就引入了队列这一属性它最大的作用就是当虚引用的对象被回收的时候就会给队列发一个信号说明一下虚引用的对象引用失效了此时JVM内部的GC垃圾回收就会对队列做判断如果满足一定的条件那么JVM的垃圾回收器就会把JVM内存中的垃圾对象堆外操作系统中的垃圾对象一并回收就不会造成内存泄露的情况了。(这里补充一点Java的GC垃圾回收是由C语言编写的可以直接操控计算机底层不仅可以清除JVM内存的垃圾也可以清除总操作系统内存的垃圾)六. 总结引用类型创建方式GC 回收时机能否通过get()获取对象主要用途是否依赖引用队列内存泄漏风险强引用Object obj new Object()永不回收只要引用链存在✅ 能普通业务对象不需要极高这是 OOM 的主要原因软引用SoftReferenceString ref new SoftReference(new String(a));内存不足时OOM 前✅ 能实现内存敏感缓存如图片缓存、大文件缓存可选配合使用可清理失效引用中如果软引用对象过多且不及时清理可能引发频繁 FGC弱引用WeakReferenceString ref new WeakReference(new String(b));发生 YGC 时只要垃圾回收器扫描到✅ 能但可能很快变 nullThreadLocal 的 Key防止 Key 泄漏、WeakHashMap可选低但如果Value清理不及时仍会引发内存泄漏如 ThreadLocal虚引用PhantomReferenceObject ref new PhantomReference(new Object(), queue);任何时候都可能无法通过 get 感知❌不能始终返回null追踪 GC 回收管理堆外内存DirectByteBuffer必须使用通过队列感知对象何时被回收低主要用于资源释放