Appearance
引用类型
前置知识
在阅读本章前,你需要了解:
- Java 的垃圾回收机制基础
- 对象的生命周期与内存管理
- 基础的面向对象编程概念
为什么需要引用类型?
想象一下你在开发一个大型的 Java 应用,需要管理大量对象。这些对象可能消耗不同级别的内存资源,有些是必不可少的,有些则是“可有可无”的缓存数据。如果我们全都用最强的引用——强引用来管理它们,垃圾回收(GC)就很难回收不再使用的对象,可能导致内存泄漏。相反,如果引用“太弱”,又担心程序过早丢失重要数据。
Java 提供了不同级别的引用类型——强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)——它们就像是给 GC 发出的“信号”:你可以把对象当成“坚不可摧”、“心怀宽容”、“易碎品”甚至“观察员”。
这对于细粒度控制对象回收,特别是实现缓存、内存敏感的应用场景极为关键。我们这章就来逐步剖析这些引用,看看它们在实际代码中怎么用。
强引用(Strong Reference)
什么是强引用?
最“普通”的引用,就是我们平时用的那种。比如:
java
String name = "Claude";这里,name 就是一个强引用。只要它还在,垃圾回收器就不会回收它指向的对象。
为什么需要强引用?
在绝大多数代码中,我们用的都是强引用,因为这是默认行为。它保证了我们创建的对象不会无缘无故被收回,我们可以放心使用它。
下面这段代码会创建一个对象,并通过强引用引用它:
java
public class StrongReferenceDemo {
public static void main(String[] args) {
String strongRef = new String("I am a strong reference");
System.out.println(strongRef);
}
}这段代码做了什么:
- 创建了一个
String对象,内容是"I am a strong reference"。 strongRef变量强引用这个对象。- 打印出字符串。
- 在这里,对象在
strongRef还活着时不会被 GC 回收。
小结
强引用简单,安全,常用。但它不适合做缓存,否则容易造成内存占用过高。
软引用(Soft Reference)
什么是软引用?
软引用比强引用“弱”一点。它们才是内存敏感缓存的好帮手。JVM 会尽可能保留软引用指向的对象,但当内存不足时,会优先回收这些软引用对象。
用一句话来说,软引用是“内存撑得住的时候留下,撑不住马上收”。
软引用的基本用法
Java 提供了java.lang.ref.SoftReference类来创建软引用。看下面的例子:
java
import java.lang.ref.SoftReference;
public class SoftReferenceDemo {
public static void main(String[] args) {
String strongRef = new String("I am strong");
SoftReference<String> softRef = new SoftReference<>(strongRef);
System.out.println("Soft reference points to: " + softRef.get());
strongRef = null; // 取消强引用
System.gc(); // 尝试调用 GC
System.out.println("After GC, soft reference points to: " + softRef.get());
}
}这段代码做了什么:
- 创建了一个强引用的字符串对象。
- 创建一个指向同一字符串的软引用。
- 打印软引用指向的对象(期望打印字符串内容)。
- 将强引用设为
null,意味着该对象只通过软引用可达。 - 调用
System.gc()请求 GC(不保证立刻回收)。 - 再次打印软引用指向的对象。
如果当前内存压力不大,软引用对象可能还存在;如果内存紧张,GC 会回收它。
场景参考:
- 缓存数据,比如图片、文件内容
- 当内存吃紧时自动释放缓存,以防OOM
弱引用(Weak Reference)
什么是弱引用?
弱引用比软引用更“脆弱”。只要垃圾回收器扫描时发现你只用弱引用指向一个对象,就立刻回收它。换句话说,只要无强引用,哪怕内存很宽裕,弱引用指向的对象立刻变成垃圾。
为什么需要弱引用?
弱引用主要用于规范设计中,比如实现基于引用队列的缓存,或者维护监听器集合。它可以避免内存泄漏:
- 监听器如果用强引用,可能导致监听器无法被回收
- 弱引用可让监听器自动“下岗”
代码示例:弱引用和对象回收
java
import java.lang.ref.WeakReference;
public class WeakReferenceDemo {
public static void main(String[] args) {
String strongRef = new String("I'm strong");
WeakReference<String> weakRef = new WeakReference<>(strongRef);
System.out.println("Weak ref before nulling strong ref: " + weakRef.get());
strongRef = null; // 断开强引用
System.gc(); // 呼叫 GC
System.out.println("Weak ref after GC: " + weakRef.get()); // 很可能返回 null
}
}这段代码做了什么:
- 创建一个强引用的字符串。
- 创建对应的弱引用。
- 打印弱引用指向的对象(正常可得)。
- 断开强引用。
- 调用 GC,弱引用对象立即可能被回收。
- 再次打印弱引用结果,很可能是
null。
虚引用(Phantom Reference)
什么是虚引用?
虚引用几乎“没有引用”,只是给 GC 用于跟踪对象回收情况,不影响对象生命周期。它的 get() 方法总是返回 null。
它通常和引用队列(ReferenceQueue)配合,用于在对象被回收时收到通知,做一些资源清理工作,比如直接内存释放。
代码示例:简单的虚引用演示
java
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, refQueue);
System.out.println("Phantom reference get(): " + phantomRef.get()); // 总是 null
obj = null; // 断开强引用
System.gc();
// 等待一会儿,让GC完成
Thread.sleep(1000);
if (refQueue.poll() == phantomRef) {
System.out.println("Object has been enqueued after finalization.");
} else {
System.out.println("Object not yet enqueued.");
}
}
}这段代码做了什么:
- 创建了一个普通对象。
- 创建了引用队列和虚引用,同时虚引用关联对象和队列。
- 打印虚引用的
get(),永远是null。 - 断开强引用后调用 GC。
- 稍微等待后,检查引用队列是否收到虚引用,说明对象已被垃圾回收。
典型用途
- 管理堆外内存
- 清理直接内存、关闭资源
- 逃逸属性检测
对比总结
| 引用类型 | 是否影响GC回收 | 典型场景 | 说明 |
|---|---|---|---|
| 强引用 StrongReference | 否,GC不会回收 | 所有普通引用 | 最常用,默认引用类型 |
| 软引用 SoftReference | 内存紧张时回收 | 内存敏感缓存 | 适合缓存,用内存压力做条件 |
| 弱引用 WeakReference | 无强引用即回收 | 规范接口/监听器管理 | 防止内存泄漏的辅助工具 |
| 虚引用 PhantomReference | 对象回收后通知 | 资源清理 | 只能配合 ReferenceQueue,用于清理 |
💡 实战建议
- 用强引用管理核心业务对象,不要滥用软弱引用避免难以调试的内存问题。
- 软引用适合缓存设计,但在高并发、复杂场景下行为不可完全依赖,需要结合容量限制和过期策略。
- 弱引用最常用于事件监听器注册,避免内存泄漏。
- 虚引用一般只用于底层资源释放与与 GC 交互,普通业务不常用。
- 结合
ReferenceQueue进行回调可以更精细地控制对象生命周期。
⚠️ 常见陷阱
- 调用
System.gc()不能保证立即触发垃圾回收,切勿依赖它做业务逻辑。 - 软引用和弱引用都不是万能的缓存方案,随时可能被回收导致突然失效,要设计好容错逻辑。
- 虚引用中的
get()总是返回 null,尝试访问会误解引用含义。 - 在多线程环境使用引用队列时注意线程安全。
延伸思考
- 你有没有碰到过因为缓存没有及时释放,导致 OOM 的问题?软引用能否完全解决这个问题?为什么?
- 弱引用可以自动清理监听器,那强引用的监听器为什么会导致内存泄漏?你用过哪些模式避免这一问题?
- 虚引用虽然复杂,但它为你设计的资源管理可以带来什么好处?能举个实战例子吗?
小结
- Java 提供了四种不同“强度”的引用类型,对垃圾回收行为有不同影响。
- 强引用是默认且常用的引用,保证对象不被回收。
- 软引用适合内存敏感的缓存,GC 内存不足时回收。
- 弱引用更弱,无强引用即被回收,主用于规范化弱关系。
- 虚引用无直接获取对象能力,结合引用队列用来做回收通知和资源清理。
- 掌握这些引用类型,有助于写出健壮且内存友好的 Java 程序。
如果你印象中引用就等于“变量指向对象”,那么现在,你已经踏入更深的引用世界了。让我们带着“引用就是跟 GC 的沟通工具”这个思路,在实际项目里积极实验这些强弱软虚引用,写出既聪明又安全的内存管理代码吧!
