java面试必问25:强引用、软引用、弱引用、虚引用:从Java对象生命周期到内存优化
强引用、软引用、弱引用、虚引用从 Java 对象生命周期到内存优化一篇讲透面试官“Java 有哪几种引用类型分别有什么特点”你“强引用是永不回收OOM 也不回收软引用在内存不足时回收弱引用下一次 GC 必回收虚引用主要用于管理堆外内存必须配合引用队列。”面试官“那你用过软引用或弱引用吗WeakHashMap 是怎么工作的”你“……”很多人能背出四种引用的回收时机但一追问“为什么需要这么多引用类型”“如何利用它们优化内存”就含糊了。本文从 GC 可达性角度结合代码示例彻底讲透 Java 引用体系。一、为什么需要区分引用类型Java 垃圾回收GC的基本思想是回收“死亡”对象而对象的存活与否取决于可达性。传统的强引用Object obj new Object()会导致对象永远可达除非引用被主动置空。但在某些场景下我们希望能更灵活地控制对象的生命周期缓存希望缓存的对象在内存充足时保留内存紧张时自动释放。避免内存泄漏某些容器类持有对象引用导致对象无法被回收。堆外内存管理需要感知对象被回收以便清理堆外资源。因此Java 在 1.2 版本引入了软引用SoftReference、弱引用WeakReference和虚引用PhantomReference再加上默认的强引用StrongReference共同构成了完整的引用体系。二、可达性强度与引用类型GC 在判断对象是否可回收时会沿着引用链从 GC Roots 出发根据引用的强度进行不同处理。从强到弱依次为可达性级别含义回收时机强可达存在强引用链永不回收除非引用消失软可达存在软引用没有强引用内存不足时回收弱可达存在弱引用没有强/软引用下一次 GC 即回收虚可达存在虚引用没有其他引用随时可能回收无法通过虚引用获取对象不可达无任何引用肯定回收三、强引用Strong Reference1. 定义强引用是最常见的引用方式例如Object obj new Object()。只要强引用存在GC 就永远不会回收这个对象即使抛出OutOfMemoryError也不会回收。2. 特点默认的引用类型。显式置为null才会断开引用帮助 GC 回收。强引用导致内存泄漏的常见原因长生命周期容器如static集合持有短生命周期对象的引用。3. 示例ObjectstrongRefnewObject();// 强引用System.gc();// 不会回收 strongRef 指向的对象strongRefnull;// 断开引用对象变得可回收四、软引用Soft Reference1. 定义软引用用于描述有用但不是必须的内存缓存。在系统将要发生内存溢出之前GC 会将这些软引用对象列入回收范围进行第二次回收。如果这次回收后内存仍不足才抛出 OOM。2. 特点适合实现内存敏感的高速缓存如图片缓存、查询结果缓存。回收时机JVM 根据当前内存状况和-XX:SoftRefLRUPolicyMSPerMB参数决定。默认值 1000 意味着每 MB 堆空间中的软引用存活 1 秒基于上次访问时间。软引用对象在回收前可以被 JVM 保留一段时间不是立刻清除。3. 代码示例importjava.lang.ref.SoftReference;publicclassSoftRefDemo{publicstaticvoidmain(String[]args){ObjectobjnewObject();SoftReferenceObjectsoftRefnewSoftReference(obj);objnull;// 取消强引用// 第一次获取对象还存在System.out.println(softRef.get());// 非 null// 模拟内存压力可以通过 -Xms10m -Xmx10m 运行并分配大量内存// 在内存不足时softRef.get() 可能会返回 null}}4. 典型应用缓存实现例如 Android 的图片缓存、MyBatis 的二级缓存可选软引用。内存敏感的加载器在内存压力下释放部分资源。五、弱引用Weak Reference1. 定义弱引用的强度比软引用更低。下一次 GC 发生时无论内存是否充足弱引用指向的对象都会被回收只要没有强引用或软引用。2. 特点更短的生命周期。常用于实现规范映射canonical mapping如WeakHashMap中的键使用弱引用当键对象没有其他强引用时自动从 Map 中移除避免内存泄漏。3. 代码示例importjava.lang.ref.WeakReference;publicclassWeakRefDemo{publicstaticvoidmain(String[]args){ObjectobjnewObject();WeakReferenceObjectweakRefnewWeakReference(obj);objnull;// 取消强引用System.out.println(weakRef.get());// 非 nullSystem.gc();// 手动触发 GCSystem.out.println(weakRef.get());// 很可能输出 null}}4. 典型应用WeakHashMap键是弱引用当键只有被 Map 引用时下次 GC 会自动删除该键值对常用于缓存元数据或避免监听器泄漏。ThreadLocalThreadLocalMap中的 Entry 继承了WeakReference键ThreadLocal 实例是弱引用防止线程池中的线程存活导致 ThreadLocal 无法回收。// WeakHashMap 演示WeakHashMapObject,StringmapnewWeakHashMap();ObjectkeynewObject();map.put(key,value);System.out.println(map.size());// 1keynull;System.gc();// 稍后 map 中的条目可能已被自动清除六、虚引用Phantom Reference1. 定义虚引用是最弱的引用它无法通过 get() 方法获取实际对象始终返回null。它的唯一作用是在对象被 GC 回收时收到一个通知。虚引用必须与引用队列ReferenceQueue配合使用。2. 特点无法通过虚引用访问对象。只有虚引用指向的对象被回收后虚引用才会被放入引用队列。常用于管理堆外内存如 DirectByteBuffer 的 Cleaner 机制在对象回收时执行清理动作。3. 代码示例importjava.lang.ref.PhantomReference;importjava.lang.ref.ReferenceQueue;publicclassPhantomRefDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{ObjectobjnewObject();ReferenceQueueObjectrefQueuenewReferenceQueue();PhantomReferenceObjectphantomRefnewPhantomReference(obj,refQueue);objnull;// 取消强引用System.out.println(phantomRef.get());// 总是 nullSystem.gc();// 稍后从队列中取出虚引用表示对象已回收System.out.println(refQueue.remove(1000));// 可能为 phantomRef}}4. 典型应用堆外内存回收Netty、DirectByteBuffer 使用sun.misc.Cleaner虚引用子类来释放堆外内存。资源清理当对象被回收时需要关闭文件、释放网络连接等但一般使用try-finally或try-with-resources更可靠。注意虚引用的清理动作通常由 JVM 内部线程执行开发者很少直接使用。JDK 9 引入了Cleaner类比finalize()更安全。七、引用队列ReferenceQueue1. 定义引用队列是引用的“通知机制”。当软引用、弱引用、虚引用所引用的对象被 GC 回收后这些引用对象SoftReference等实例本身会被加入关联的引用队列。开发者可以轮询队列进行一些后置清理工作。2. 使用方式ReferenceQueueObjectqueuenewReferenceQueue();WeakReferenceObjectweakRefnewWeakReference(obj,queue);// 当 obj 被回收后weakRef 会被自动放入 queue3. 作用避免内存泄漏及时清除不再使用的引用对象这些引用对象本身也占用内存。执行额外清理例如虚引用清理堆外内存。八、对比总结引用类型回收时机能否获取对象典型应用是否需要引用队列强引用永不回收除非断开能普通对象引用否软引用内存不足时能内存敏感缓存可选用于清除过期缓存弱引用下次 GC 必回收能WeakHashMap、ThreadLocal可选虚引用任何时候无法获取不能堆外内存清理必须九、常见面试追问Q1软引用和弱引用的区别软引用只有内存不足时才回收弱引用只要 GC 就回收。因此软引用更适合实现缓存希望尽量保留弱引用更适合规范映射不阻碍对象回收。Q2WeakHashMap的工作原理是什么WeakHashMap的 Entry 继承了WeakReference键是弱引用。当键对象不再被外部强引用时GC 会回收该键同时将 Entry 放入引用队列。WeakHashMap在读取时会自动清理这些无效 Entry所以不需要手动删除。Q3为什么要有虚引用用finalize()不行吗finalize()方法执行不确定且会导致对象“复活”性能差且已被 JDK 9 废弃。虚引用提供了一种确定、低开销的回收通知机制并且不干扰对象的生命周期。Cleaner是虚引用的升级版。Q4软引用何时被回收可以控制回收优先级吗通过 JVM 参数-XX:SoftRefLRUPolicyMSPerMBms可以调整软引用的“存活时间”。默认 1000 毫秒表示每 1 MB 堆空间允许软引用在最后一次访问后存活 1 秒。该值越大软引用越不容易被回收。Q5如何选择使用哪种引用普通对象 → 强引用实现缓存希望尽量保留但内存紧张时释放 → 软引用实现 Map 的键不阻碍键对象回收 → 弱引用需要在对象回收时执行清理且无法通过 finalize 实现 → 虚引用 引用队列十、总结引用类型一句话总结强引用只要我不放手你就别想走软引用内存还有你就留着不够了就卖掉弱引用下次打扫卫生你就会消失虚引用我看不见你但知道你已经走了理解这四种引用类型不仅能帮你写出更健壮的缓存和避免内存泄漏还能深入理解 JVM 和 Android 的内存管理机制。希望这篇文章能帮你彻底掌握这个高频考点欢迎继续讨论。