基于本文回答
0
评论

Java 中的四种引用类型(强引用、软引用、弱引用、虚引用)及其对应的使用场景

知识点图片

在 Java 中,从 JDK 1.2 开始引入了 java.lang.ref 包,将对象的引用分为四个级别:强引用(Strong)、软引用(Soft)、弱引用(Weak)和虚引用(Phantom)

这种设计的核心目的是为了让程序员能够按需控制对象的生命周期,并与垃圾回收器(GC)更好地交互,从而避免内存泄漏(Memory Leak)和内存溢出(OOM)。

以下是对这四种引用类型的详细解析及其对应的使用场景:


1. 强引用 (Strong Reference)

概念:
强引用是 Java 中最常见、最默认的引用类型。当我们使用 new 关键字创建一个对象并赋值给一个变量时,这个变量就是强引用。

GC 回收时机:
绝对不回收。 只要一个对象有强引用指向它,垃圾回收器(GC)宁愿抛出 OutOfMemoryError (OOM) 导致程序崩溃,也绝对不会回收该对象。只有当强引用被显式赋值为 null,或者超出了变量的作用域时,对象才会被当作垃圾回收。

代码示例:

java
// 强引用
Object obj = new Object(); 
String str = "Hello World";

使用场景:

  • 日常开发中的绝大多数场景。只要是你必须保证不能丢失的核心业务数据和对象,都使用强引用。

2. 软引用 (Soft Reference)

概念:
软引用用来描述一些有用但非必需的对象。在 Java 中使用 java.lang.ref.SoftReference 类来实现。

GC 回收时机:
内存不足时回收。 当系统内存充足时,GC 不会回收软引用对象;但是当 JVM 内存不足(即即将发生 OOM 之前),GC 会将软引用指向的对象列入回收范围并进行回收。如果回收后内存依然不足,才会抛出 OOM。

代码示例:

java
// 强引用
Object obj = new Object();
// 包装成软引用
SoftReference<Object> softRef = new SoftReference<>(obj);
// 切断强引用,此时对象只有软引用
obj = null; 

// 获取对象(如果内存充足,可以获取到;如果刚刚经历了内存不足的 GC,会返回 null)
Object value = softRef.get();

使用场景:

  • 内存敏感的缓存系统。例如:网页缓存、图片缓存(如早期的 Android 图片加载框架经常用软引用缓存 Bitmap)。
  • 逻辑:如果内存够用,就把这些图片存在内存里,读取速度快;如果内存紧张了,就把这些图片清掉腾出空间,大不了下次用的时候再从磁盘/网络重新加载。

3. 弱引用 (Weak Reference)

概念:
弱引用也是用来描述非必需对象的,但它的强度比软引用更弱。在 Java 中使用 java.lang.ref.WeakReference 类来实现。

GC 回收时机:
发现即回收(无论内存是否充足)。 当 GC 线程扫描到只具有弱引用的对象时,无论当前系统内存空间是否足够,都会将它回收。由于 GC 线程优先级较低,可能不会立刻发现弱引用,所以它还能存活一小段时间。

代码示例:

java
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);
obj = null; // 切断强引用

// 下一次 GC 之前,可能还能 get() 到对象。GC 发生后,get() 必定为 null
Object value = weakRef.get();

使用场景:

  • 解决特定情况下的内存泄漏问题
  • WeakHashMap:Java 提供的专门基于弱引用实现的 Map。如果 Map 的 Key 被回收了,该 Key 对应的键值对也会被内部机制清除。常用于保存对象的元数据或附加信息(对象没了,附加信息也就没意义了)。
  • ThreadLocal 的底层实现ThreadLocalMap 中的 Entry 的 Key 使用了对 ThreadLocal 对象的弱引用。这样当外部没有强引用指向 ThreadLocal 时,它可以被 GC 回收,防止在线程池场景下造成内存泄漏(注意:Value 是强引用,仍需要手动 remove())。

4. 虚引用 (Phantom Reference)

概念:
也叫“幽灵引用”,是最弱的一种引用关系。在 Java 中使用 java.lang.ref.PhantomReference 类来实现。一个对象是否有虚引用,完全不会对其生存时间构成影响

GC 回收时机:
随时会被回收。 与软引用和弱引用不同,你根本无法通过虚引用来获取对象实例(其 get() 方法永远返回 null)。虚引用必须和引用队列(ReferenceQueue)联合使用。

代码示例:

java
Object obj = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
// 必须传入 ReferenceQueue
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);
obj = null;

// phantomRef.get() 永远返回 null
System.out.println(phantomRef.get()); 

使用场景:
虚引用的唯一目的是在这个对象被 GC 回收时收到一个系统通知(对象被回收时,它的虚引用会被放入 ReferenceQueue 中)。

  • 替代 finalize() 方法:执行对象的资源清理工作,比 finalize() 更灵活且不影响 GC 性能。
  • 管理堆外内存(Direct Memory):在 NIO(如 ByteBuffer.allocateDirect)中,Java 经常使用直接内存(操作系统内存而非 JVM 堆内存)。当代表直接内存的 Java 堆内对象被回收时,通过虚引用和引用队列(如 Cleaner 机制),可以立刻触发底层 C/C++ 代码去释放对应的操作系统物理内存,防止内存泄漏。

💡 总结与对比表格

引用类型 对应类 GC 回收时机 生存周期 get() 方法能否获取对象 常见使用场景
强引用 无(默认 new 永不回收,宁可 OOM 只要强引用在,就一直存活 核心业务对象,普通对象的创建
软引用 SoftReference 内存不足时回收 内存充足时存活,不足时死亡 能(未被回收前) 本地缓存(图片/网页缓存等)
弱引用 WeakReference 下一次 GC 时必定回收 存活到下一次 GC 发生 能(未发生 GC 前) WeakHashMapThreadLocal
虚引用 PhantomReference 随时回收 相当于没有引用 永远返回 null 堆外内存释放的跟踪、替代 finalize
右滑查看面试常问