Java四种引用类型详解
本文讲解Java强、软、弱、虚四种引用。其核心区别是:引用强度依次减弱,直接决定了对象被垃圾回收(GC)的不同时机,分别适用于常规对象、内存敏感缓存、以及特定场景的资源释放跟踪。
我们来详细、清晰地区分Java中的四种引用类型:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。
这四种引用的主要区别在于它们对Java垃圾回收(Garbage Collection, GC)的影响程度,或者说,它们所指向的对象被GC回收的“时机”不同。引用的强度依次减弱:
强引用 > 软引用 > 弱引用 > 虚引用
1. 强引用 (Strong Reference)
这是我们日常编程中最常使用、也是默认的引用类型。
特点:
- 只要一个对象有强引用指向它,垃圾回收器就绝对不会回收这个对象,无论系统内存多么紧张。
- 如果内存不足,Java虚拟机会抛出
OutOfMemoryError错误,使程序异常终止,也不会回收具有强引用的对象。
生命周期:
- 当一个对象没有任何强引用指向它时,它才可能被后续的GC回收。
代码示例:
java// new Object() 创建了一个对象,obj 就是对这个对象的强引用 Object obj = new Object(); // 只有当 obj 不再指向这个对象时,比如: obj = null; // 手动断开引用 // 或者 obj 的作用域结束时,这个对象才可能被GC回收比喻:
强引用就像你与一个物品之间的“生命纽带”。只要这根纽带存在,谁也无法拿走这个物品。想丢掉它,必须先把纽带剪断 (obj = null)。
2. 软引用 (Soft Reference)
软引用用来描述一些还有用但并非必需的对象。
特点:
- 当一个对象只有软引用指向它时,它会在内存即将不足(即将发生
OutOfMemoryError)之前被垃圾回收器回收。 - 如果GC回收了这些对象之后,内存仍然不足,才会抛出
OutOfMemoryError。
- 当一个对象只有软引用指向它时,它会在内存即将不足(即将发生
生命周期:
- 比强引用弱。只要内存充足,它就不会被回收;内存不足时,它就会被回收。
主要用途:
- 实现内存敏感的高速缓存。例如,网页缓存、图片缓存等。
- 缓存的逻辑是:如果内存足够,就保留缓存,加快访问速度;如果内存不足,就清掉这些缓存,以保证程序核心功能的运行,避免程序崩溃。
代码示例:
javaimport java.lang.ref.SoftReference; // 创建一个强引用的对象 String strongRef = new String("hello"); // 创建一个指向该对象的软引用 SoftReference<String> softRef = new SoftReference<>(strongRef); // 断开强引用,现在只有软引用指向这个 "hello" 字符串对象了 strongRef = null; // 当内存充足时,可以通过 get() 方法获取到对象 System.out.println(softRef.get()); // 输出 "hello" // 当系统内存紧张时,GC会回收软引用对象 // 此时再次调用 get() 方法会返回 null // System.gc(); // 强制GC不一定会回收软引用,取决于JVM内存情况比喻:
软引用就像你家里的“闲置物品”。屋子(内存)宽敞时,你就留着它。当有更重要的东西要放进来,屋子不够大了,你就会先把这些闲置物品扔掉,腾出空间。
3. 弱引用 (Weak Reference)
弱引用的强度比软引用更弱,它描述的是非必需的对象。
特点:
- 当一个对象只有弱引用指向它时,无论当前内存是否充足,只要垃圾回收器运行,这个对象就一定会被回收。
生命周期:
- 只能“存活”到下一次GC发生之前。
主要用途:
WeakHashMap是一个典型的应用。WeakHashMap的Key是弱引用。当一个Key对象没有其他强引用指向它时,GC会回收它,WeakHashMap也会自动移除对应的键值对。- 防止内存泄漏,特别是在监听器和回调函数的场景中。如果一个对象注册了监听器,但之后不再使用,如果没有妥善处理,监听器会持有该对象的引用,导致其无法被回收。使用弱引用可以解决这个问题。
代码示例:
javaimport java.lang.ref.WeakReference; String strongRef = new String("hello"); WeakReference<String> weakRef = new WeakReference<>(strongRef); // 断开强引用 strongRef = null; System.out.println(weakRef.get()); // 可能输出 "hello" // 强制进行垃圾回收 System.gc(); // GC之后,弱引用所指向的对象很大概率已经被回收 System.out.println(weakRef.get()); // 输出 null比喻:
弱引用就像贴在布告栏上的“广告传单”。清洁工(GC)每次来打扫卫生时,不管布告栏是否贴满,都会把这些传单清理掉。
4. 虚引用 (Phantom Reference)
也称为“幽灵引用”或“幻影引用”,是所有引用类型中最弱的。
特点:
- 一个对象是否有虚引用,完全不影响其生命周期。它就和没有任何引用一样,在任何时候都可能被GC回收。
- 无法通过虚引用来获取对象实例。
PhantomReference的get()方法永远返回null。 - 它的唯一作用是:在这个对象被垃圾回收器回收时,能收到一个系统通知。
使用要求:
- 虚引用必须和引用队列(
ReferenceQueue)联合使用。当GC准备回收一个对象时,如果发现它还有虚引用,就会在回收该对象之前,把这个虚引用加入到与之关联的引用队列中。
- 虚引用必须和引用队列(
主要用途:
- 管理堆外内存(Direct Memory)。例如,
NIO中的DirectByteBuffer。当DirectByteBuffer对象被回收时,可以通过虚引用机制,让一个专门的线程从引用队列中获取通知,然后调用本地方法释放对应的堆外内存,从而避免内存泄漏。
- 管理堆外内存(Direct Memory)。例如,
代码示例:
javaimport java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; ReferenceQueue<Object> queue = new ReferenceQueue<>(); Object obj = new Object(); PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue); // get() 方法永远返回 null System.out.println(phantomRef.get()); // 输出 null // 断开强引用 obj = null; // 在某个时刻GC运行时,obj被回收,phantomRef会被加入到queue中 System.gc(); // 在未来的某个时间点,可以从队列中取出虚引用,说明其指向的对象已经被回收 // 实际应用中会有一个专门的线程来轮询这个队列 // if (queue.poll() != null) { // System.out.println("对象已被GC回收,可以执行清理操作了"); // }比喻:
虚引用就像一份“死亡通知书”。你无法用它来“复活”死者(获取对象),它的唯一作用就是告诉你“这个人已经去世了,你可以处理后事了”(对象已被回收,你可以清理相关联的外部资源了)。
总结对比
| 引用类型 | 回收时机 | 强度 | 主要用途 | get()方法 |
|---|---|---|---|---|
| 强引用 (Strong) | 从不回收(除非无引用) | 最强 | 普通的对象引用 | 返回对象 |
| 软引用 (Soft) | 内存不足时回收 | 较强 | 内存敏感的缓存 | 可能返回 null |
| 弱引用 (Weak) | 下一次GC时回收 | 较弱 | WeakHashMap、防止内存泄漏 |
可能返回 null |
| 虚引用 (Phantom) | 和没有引用一样,随时可回收 | 最弱 | 管理堆外内存、跟踪对象回收 | 总是返回 null |