Skip to content

引用类型

前置知识

在阅读本章前,你需要了解:

  • 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);
    }
}

这段代码做了什么:

  1. 创建了一个 String 对象,内容是 "I am a strong reference"
  2. strongRef 变量强引用这个对象。
  3. 打印出字符串。
  4. 在这里,对象在 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());
    }
}

这段代码做了什么:

  1. 创建了一个强引用的字符串对象。
  2. 创建一个指向同一字符串的软引用。
  3. 打印软引用指向的对象(期望打印字符串内容)。
  4. 将强引用设为 null,意味着该对象只通过软引用可达。
  5. 调用 System.gc() 请求 GC(不保证立刻回收)。
  6. 再次打印软引用指向的对象。

如果当前内存压力不大,软引用对象可能还存在;如果内存紧张,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
    }
}

这段代码做了什么:

  1. 创建一个强引用的字符串。
  2. 创建对应的弱引用。
  3. 打印弱引用指向的对象(正常可得)。
  4. 断开强引用。
  5. 调用 GC,弱引用对象立即可能被回收。
  6. 再次打印弱引用结果,很可能是 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.");
        }
    }
}

这段代码做了什么:

  1. 创建了一个普通对象。
  2. 创建了引用队列和虚引用,同时虚引用关联对象和队列。
  3. 打印虚引用的 get(),永远是null。
  4. 断开强引用后调用 GC。
  5. 稍微等待后,检查引用队列是否收到虚引用,说明对象已被垃圾回收。

典型用途

  • 管理堆外内存
  • 清理直接内存、关闭资源
  • 逃逸属性检测

对比总结

引用类型是否影响GC回收典型场景说明
强引用 StrongReference否,GC不会回收所有普通引用最常用,默认引用类型
软引用 SoftReference内存紧张时回收内存敏感缓存适合缓存,用内存压力做条件
弱引用 WeakReference无强引用即回收规范接口/监听器管理防止内存泄漏的辅助工具
虚引用 PhantomReference对象回收后通知资源清理只能配合 ReferenceQueue,用于清理

💡 实战建议

  • 用强引用管理核心业务对象,不要滥用软弱引用避免难以调试的内存问题。
  • 软引用适合缓存设计,但在高并发、复杂场景下行为不可完全依赖,需要结合容量限制和过期策略。
  • 弱引用最常用于事件监听器注册,避免内存泄漏。
  • 虚引用一般只用于底层资源释放与与 GC 交互,普通业务不常用。
  • 结合 ReferenceQueue 进行回调可以更精细地控制对象生命周期。

⚠️ 常见陷阱

  • 调用 System.gc() 不能保证立即触发垃圾回收,切勿依赖它做业务逻辑。
  • 软引用和弱引用都不是万能的缓存方案,随时可能被回收导致突然失效,要设计好容错逻辑。
  • 虚引用中的 get() 总是返回 null,尝试访问会误解引用含义。
  • 在多线程环境使用引用队列时注意线程安全。

延伸思考

  • 你有没有碰到过因为缓存没有及时释放,导致 OOM 的问题?软引用能否完全解决这个问题?为什么?
  • 弱引用可以自动清理监听器,那强引用的监听器为什么会导致内存泄漏?你用过哪些模式避免这一问题?
  • 虚引用虽然复杂,但它为你设计的资源管理可以带来什么好处?能举个实战例子吗?

小结

  • Java 提供了四种不同“强度”的引用类型,对垃圾回收行为有不同影响。
  • 强引用是默认且常用的引用,保证对象不被回收。
  • 软引用适合内存敏感的缓存,GC 内存不足时回收。
  • 弱引用更弱,无强引用即被回收,主用于规范化弱关系。
  • 虚引用无直接获取对象能力,结合引用队列用来做回收通知和资源清理。
  • 掌握这些引用类型,有助于写出健壮且内存友好的 Java 程序。

如果你印象中引用就等于“变量指向对象”,那么现在,你已经踏入更深的引用世界了。让我们带着“引用就是跟 GC 的沟通工具”这个思路,在实际项目里积极实验这些强弱软虚引用,写出既聪明又安全的内存管理代码吧!