什么是强引用、软引用、弱引用和虚引用?
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
面试考察点
这道题考察的核心,可不仅仅是让你背诵四种引用的定义。面试官想通过它,由浅入深地评估你对 JVM 内存管理和垃圾回收的理解深度。具体来说,他想了解:
- 你对 Java 对象生命周期和可达性分析的理解:不仅仅是知道引用类型,更要明白它们如何影响 GC 对对象“存活”或“可回收”的判断。
- 你是否掌握不同引用类型的实际应用场景:在实际开发中,能否利用它们来解决内存敏感型缓存、避免内存泄漏等问题。
- 你对引用与 GC 交互机制的了解:例如,是否知道软/弱引用适合与
ReferenceQueue配合使用,来监听对象被回收的事件。 - 你是否有底层原理的探索精神:是否深入想过这些引用在 JVM 层面是如何实现的(比如通过
java.lang.ref包下的类)。
核心答案
简单来说,JVM 中的四种引用强度依次递减,它们决定了对象在垃圾回收时的 “命运”:
- 强引用(Strong Reference):最常见的引用(如
Object obj = new Object())。只要强引用还存在,垃圾收集器就永远不会回收被引用的对象。 - 软引用(Soft Reference):描述一些还有用但非必需的对象。在系统将要发生内存溢出异常之前,这些对象会被列入回收范围进行第二次回收。如果这次回收后还没有足够内存,才会抛出 OOM。适合做内存敏感的高速缓存。
- 弱引用(Weak Reference):描述非必需对象,强度比软引用更弱。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
- 虚引用(Phantom Reference):最弱的引用,也称为 “幽灵引用” 或 “幻影引用”。为一个对象设置虚引用关联的唯一目的,就是能在这个对象被收集器回收时收到一个系统通知。无法通过虚引用来取得一个对象实例。
深度解析
原理/机制
这四种引用在 JVM 中主要通过 java.lang.ref 包下的 SoftReference、WeakReference 和 PhantomReference 三个类(以及它们的基类 Reference)来实现。它们的核心行为与垃圾回收算法中的可达性分析息息相关。
- 可达性级别:JVM 会从 GC Roots 出发,根据引用的链条来判断对象是否可达。不同引用类型定义了不同强度的可达性,比如强可达(Strongly Reachable)、软可达(Softly Reachable)、弱可达(Weakly Reachable)等。
- GC 处理策略:当 GC 标记对象时,会根据其可达性级别决定是否回收:
- 只有强可达的对象是绝对不会被回收的。
- 在 GC 线程发现某个对象仅剩下软引用(即没有强引用,只有软引用链),且内存空间紧张时,该对象会被回收。
- 对于弱引用对象,无论内存是否充足,GC 线程在扫描到它时都会将其回收。
- 虚引用完全不影响对象的生命周期,它存在的唯一价值就是跟踪对象的回收,所以对象在任何时候都可能被回收。
另外,这些引用都可以和一个 ReferenceQueue(引用队列)关联。当引用对象所引用的对象被回收后,JVM 会将该引用对象(如 WeakReference 的实例本身)加入到这个队列中,程序可以通过监控队列来获知对象被回收的事件。
代码示例
import java.lang.ref.*;
public class ReferenceDemo {
public static void main(String[] args) {
// 1. 强引用
Object strongRef = new Object();
System.out.println("强引用对象:" + strongRef);
// 2. 软引用
Object obj = new Object();
SoftReference<Object> softRef = new SoftReference<>(obj);
obj = null; // 去掉强引用
System.gc(); // 主动触发 GC(仅仅是建议,不一定立即执行)
System.out.println("软引用对象(内存充足时,可能还在):" + softRef.get());
// 3. 弱引用(配合引用队列)
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object obj2 = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj2, queue);
obj2 = null; // 去掉强引用
System.gc();
System.out.println("弱引用对象(GC 后应为 null):" + weakRef.get());
// 检查队列,看弱引用对象是否已被加入队列
Reference<?> refFromQueue = queue.poll();
if (refFromQueue != null) {
System.out.println("弱引用对象已被加入引用队列:" + refFromQueue);
}
// 4. 虚引用
ReferenceQueue<Object> phantomQueue = new ReferenceQueue<>();
Object obj3 = new Object();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj3, phantomQueue);
obj3 = null;
System.gc();
// 虚引用的 get() 方法始终返回 null
System.out.println("虚引用对象:" + phantomRef.get());
Reference<?> refFromPhantomQueue = phantomQueue.poll();
if (refFromPhantomQueue != null) {
System.out.println("虚引用对象已被加入引用队列,说明原对象已被回收");
}
}
}
对比分析
| 引用类型 | 强度 | 垃圾回收时机 | 主要用途 | 能否通过 get() 获取对象 |
|---|---|---|---|---|
| 强引用 | 最强 | 永不回收(只要还有强引用) | 常规对象引用 | 可以 |
| 软引用 | 中等 | 内存不足时回收 | 内存敏感缓存(如图片缓存) | 可以 |
| 弱引用 | 较弱 | 下次 GC 时必回收 | 防止内存泄漏(如 ThreadLocal 的 Entry) | 可以 |
| 虚引用 | 最弱 | 随时可能回收,与 GC 无关 | 跟踪对象被回收(如 NIO DirectBuffer 清理) | 永远不行 |
最佳实践
-
软引用实现缓存:当我们需要缓存一些大对象(比如图片、从数据库加载的数据),但又担心它们占用太多内存时,可以使用
SoftReference。这样,在内存紧张时,JVM 会自动回收这些缓存对象。// 一个简单的软引用缓存 public class SoftCache<K, V> { private Map<K, SoftReference<V>> cache = new HashMap<>(); public V get(K key) { SoftReference<V> ref = cache.get(key); if (ref == null) return null; V value = ref.get(); if (value == null) { cache.remove(key); // 对象已被回收,移除过时的 entry } return value; } public void put(K key, V value) { cache.put(key, new SoftReference<>(value)); } } -
弱引用防止内存泄漏:最典型的应用就是
ThreadLocal。ThreadLocalMap中的 Entry 继承自WeakReference,其 key 是ThreadLocal实例的弱引用。这样做的好处是,当ThreadLocal对象不再有强引用时,下次 GC 时 key 就会被回收,避免了ThreadLocal对象无法被回收导致的内存泄漏。 -
虚引用 + 引用队列管理堆外内存:Netty 等框架使用
PhantomReference来追踪 DirectBuffer(堆外内存)的回收。当 DirectBuffer 对象被 GC 时,对应的虚引用会被放入队列,然后通过一个专门的线程去清理对应的堆外内存,从而确保系统资源被正确释放。
常见误区
- 误区 1:认为弱引用会立即被回收
注意,弱引用对象是在 GC 运行时才会被回收,并非设置弱引用后对象就立刻消失了。GC 的触发时机由 JVM 决定。
- 误区 2:软引用在内存充足时也可能会被回收
虽然软引用的回收时机是“内存不足时”,但在某些 JVM 实现中(特别是对存活时间较长的软引用),即使内存充足,也可能被回收以维持内存的稳定。所以不能依赖软引用来保证对象一定存活。
- 误区 3:使用虚引用来获取对象
PhantomReference的get()方法永远返回null。它的设计目的不是让你获取对象,而是让你知道对象已经被回收了。 - 误区 4:忽略引用队列的清理工作
如果不定期从引用队列中取出并处理那些已入队的引用对象,可能会导致
Reference对象本身占据内存,甚至引发ReferenceQueue无限增长的问题。
总结
强、软、弱、虚四种引用,从强到弱定义了对象与 GC 之间的可达性关系。理解它们不仅能帮你通过面试,更是编写高健壮性、低内存泄漏风险程序的基础。在实际开发中,根据对象的生命周期和内存敏感度选择合适的引用类型,是 Java 高级工程师必备的技能之一。