Java 的四种引用类型
Java 从 JDK 1.2 开始引入 java.lang.ref 包,提供了四种引用强度递减的引用类型,用于更灵活地控制对象的生命周期和垃圾回收行为。
一、强引用(Strong Reference)
最常见的引用方式,就是普通的对象赋值。
java
Object obj = new Object(); // obj 就是强引用
特点:
- 只要强引用还存在,GC 绝不会回收被引用的对象
- 即使内存不足导致 OOM,也不会回收强引用对象
- 强引用是造成内存泄漏的主要原因
内存泄漏示例:
java
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]); // 强引用累积,最终 OOM
}
打破强引用: 显式置空 obj = null,或让其超出作用域。
二、软引用(SoftReference)
用于描述一些"有用但非必需"的对象,适合做缓存。
java
SoftReference<byte[]> softRef = new SoftReference<>(new byte[1024 * 1024]);
byte[] data = softRef.get(); // 获取对象,可能为 null
回收策略:
- 内存充足时:不会被回收
- 内存不足时(即将 OOM 前):GC 会回收软引用对象
- 回收后
get()返回null
典型用途: 图片缓存、网页缓存等对内存敏感的场景。
java
// 软引用缓存示例
Map<String, SoftReference<BufferedImage>> cache = new HashMap<>();
BufferedImage getImage(String path) {
SoftReference<BufferedImage> ref = cache.get(path);
BufferedImage img = (ref != null) ? ref.get() : null;
if (img == null) {
img = loadFromDisk(path);
cache.put(path, new SoftReference<>(img));
}
return img;
}
⚠️ 注意:软引用可与
ReferenceQueue配合,在对象被回收后收到通知。
三、弱引用(WeakReference)
比软引用更弱,只要发生 GC,无论内存是否充足都会被回收。
java
WeakReference<Object> weakRef = new WeakReference<>(new Object());
Object obj = weakRef.get(); // GC 后可能为 null
特点:
- 生命周期短暂,只能存活到下一次 GC 之前
- 一旦 GC 发生,弱引用对象立即被回收
典型用途:
ThreadLocal中的ThreadLocalMap的 key 使用弱引用WeakHashMap(key 为弱引用,key 被回收后 entry 自动移除)- 避免内存泄漏的监听器/回调注册
java
// WeakHashMap 示例:key 被 GC 后 entry 自动消失
WeakHashMap<Object, String> map = new WeakHashMap<>();
Object key = new Object();
map.put(key, "value");
key = null; // 断开强引用
System.gc(); // 触发 GC
// 此时 map 中的 entry 会被移除
四、虚引用(PhantomReference)
最弱的引用,形同虚设。
java
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object obj = new Object();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);
obj = null;
System.out.println(phantomRef.get()); // 永远返回 null
特点:
get()永远返回 null,无法通过虚引用访问对象- 必须配合
ReferenceQueue使用 - 唯一作用:在对象被回收时收到一个系统通知,用于在对象销毁前做清理工作
- 对象被回收的时机:虚引用入队时,对象实际上已不可达但尚未真正释放内存(可在此阶段做资源释放)
典型用途:
- 管理堆外内存(如
DirectByteBuffer的 Cleaner 机制就基于虚引用) - 替代
finalize()做对象销毁前的清理
java
// 虚引用 + 队列:对象被回收时收到通知
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
PhantomReference<byte[]> ref = new PhantomReference<>(new byte[1024], queue);
new Thread(() -> {
try {
Reference<? extends byte[]> r = queue.remove();
System.out.println("对象即将被回收,做清理工作");
} catch (InterruptedException e) { }
}).start();
五、四种引用对比
| 引用类型 | 类 | GC 回收时机 | get() |
典型用途 |
|---|---|---|---|---|
| 强引用 | 普通赋值 | 永不回收(除非不可达) | 对象本身 | 普通对象 |
| 软引用 | SoftReference |
内存不足时回收 | 可能为 null | 内存敏感缓存 |
| 弱引用 | WeakReference |
下次 GC 时回收 | 可能为 null | WeakHashMap、ThreadLocal |
| 虚引用 | PhantomReference |
任何时候,配合队列 | 恒为 null | 堆外内存清理、资源回收通知 |

六、引用队列(ReferenceQueue)
除强引用外,其余三种引用都可关联 ReferenceQueue。当引用指向的对象被 GC 回收后,引用对象本身会被加入队列,程序可通过轮询队列得知回收事件。
java
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakReference<Object> ref = new WeakReference<>(new Object(), queue);
// GC 后,ref 会被放入 queue
Reference<?> r = queue.poll(); // 获取已回收的引用
关键点:入队的是引用对象(Reference),不是被引用的对象,因为后者已被回收。
七、补充:finalize() 与虚引用
早期 Java 用 finalize() 做对象销毁前清理,但存在性能差、执行时机不确定、可能导致对象"复活"等问题,已被标记废弃(JDK 9+)。虚引用 + Cleaner 是官方推荐的替代方案。
java
// JDK 自带的 Cleaner(基于虚引用实现)
Cleaner cleaner = Cleaner.create();
cleaner.register(obj, () -> System.out.println("清理资源"));
总结一句话
强引用 保命,软引用 挤牙膏(内存不够才丢),弱引用 一碰 GC 就没,虚引用只负责"报丧"(通知回收)。
实际开发中,软引用/弱引用常用于缓存和避免内存泄漏,虚引用多用于底层资源管理,日常业务代码以强引用为主。