什么是强引用、软引用、弱引用和虚引用?


一则或许对你有用的小广告

欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书

  • 《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于 Spring AI + Spring Boot 3.x + JDK 21...查看介绍

  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...查看介绍;演示链接:http://116.62.199.48:7070/

  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/

  • 新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍

截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观

面试考察点

  1. 基础掌握度:面试官不仅仅是想知道你能不能背出四种引用的名字,更是想看你能不能把每种引用的生命周期、回收时机、实际用途讲清楚。

  2. GC 机制理解深度:四种引用和垃圾回收息息相关。如果你能说清楚 "SoftReference 在内存不足时才回收" 和 "WeakReference 下次 GC 就回收" 的区别,说明你真正理解了 GC 的工作机制,而不只是背了八股文。

  3. 实践应用意识:能不能举出实际使用场景,比如 WeakHashMapThreadLocal 的弱引用设计、缓存方案中的 SoftReference 等,这是区分 "背过" 和 "用过" 的关键。

核心答案

Java 提供了 4 种引用级别,从强到弱依次是:

引用类型 回收时机 对应类 典型用途
强引用(Strong) 永远不回收(只要引用还在) 无(默认) 日常编程中的普通引用
软引用(Soft) 内存不足时回收 SoftReference 缓存
弱引用(Weak) 下次 GC 时回收 WeakReference WeakHashMapThreadLocal
虚引用(Phantom) 随时可能回收,无法获取对象 PhantomReference 跟踪 GC 回收、管理堆外内存

一句话:引用越 "弱",GC 对它越不留情面。

深度解析

一、强引用(Strong Reference)

强引用就是咱们日常写代码时最普通的引用:

Object obj = new Object(); // obj 就是一个强引用

只要强引用还存在,垃圾收集器就 绝对不会 回收被引用的对象,哪怕内存撑爆了抛出 OOM,也不会动它。这是最强硬的引用关系。

要想让对象能被回收,得手动断开引用:

obj = null; // 断开强引用,GC 才有可能回收该对象

二、软引用(Soft Reference)

软引用通过 SoftReference 类实现,用来描述一些 "还有用但非必需" 的对象:

SoftReference<byte[]> softRef = new SoftReference<>(new byte[1024 * 1024 * 10]);

// 获取对象
byte[] data = softRef.get();

// 如果内存不够了,GC 会自动回收,此时 get() 返回 null

回收规则:在内存充足时,软引用对象不会被回收;当内存不够用(即将 OOM)时,GC 才会把这些软引用指向的对象干掉。

这个特性让它非常适合做 内存敏感型缓存。比如图片缓存:内存够用的时候就留着,不够了自动清理,比 LRU 省心。不过说实话,现在生产环境用专门的缓存框架(Caffeine、Guava Cache)更靠谱,手写软引用缓存还是比较原始的方式。

三、弱引用(Weak Reference)

弱引用通过 WeakReference 类实现,比软引用更 "弱":

WeakReference<Object> weakRef = new WeakReference<>(new Object());

System.gc(); // 只要 GC 一跑

weakRef.get(); // 大概率返回 null 了

回收规则:无论内存是否充足,只要发生 GC,弱引用指向的对象就会被回收。

弱引用的经典应用场景有两个:

1. WeakHashMap

WeakHashMap<Key, Value> map = new WeakHashMap<>();
map.put(key, value);

// 当 key 没有强引用时,下次 GC 会自动清除该条目
// 这在实现 "临时映射" 或 "缓存映射" 时很有用

2. ThreadLocal 的实现

ThreadLocalMap 中的 Entry 的 key 是 WeakReference<ThreadLocal<?>>,这样当 ThreadLocal 对象没有强引用时,key 会被 GC 回收,避免内存泄漏。不过 value 还是强引用,仍然需要手动 remove(),这个坑很多人踩过。

四、虚引用(Phantom Reference)

虚引用是最弱的引用,通过 PhantomReference 类实现:

ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);

phantomRef.get(); // 永远返回 null!

虚引用有几个特点:

  • 无法通过 get() 获取对象:永远返回 null,这是它和软引用、弱引用最本质的区别
  • 必须配合 ReferenceQueue 使用:当对象被回收后,虚引用会被加入队列
  • 唯一作用:在对象被回收时收到一个 "通知",用来做资源清理

虚引用最典型的应用是 跟踪堆外内存的释放。比如 DirectByteBuffer 分配了堆外内存,当对象被 GC 回收后,通过虚引用配合 ReferenceQueue 触发 Cleaner 机制来释放堆外内存。JDK 9+ 的 Cleaner 类就是基于虚引用实现的。

五、引用级别对比图

上图展示了四种引用从强到弱的关系。整体来看:

  • 强引用 是最 "霸道" 的,只要引用还在,GC 宁可 OOM 也不回收它
  • 软引用 比较温和,会根据内存状况做取舍,内存够就留着,不够就让步
  • 弱引用 更加佛系,GC 一来就主动让位,不管内存够不够
  • 虚引用 已经完全放弃了持有权,只关心 "你回收了没" 这个通知

关键点在于:引用越弱,GC 对该对象的回收越积极;而虚引用甚至不让你再访问对象,只提供一个回收通知机制。

六、ReferenceQueue 的作用

软引用、弱引用、虚引用都可以配合 ReferenceQueue 使用:

ReferenceQueue<Object> queue = new ReferenceQueue<>();
SoftReference<Object> softRef = new SoftReference<>(new Object(), queue);

// 当对象被 GC 回收后,softRef 会被自动加入 queue
// 通过监控 queue,可以知道哪些对象被回收了
Reference<?> ref = queue.poll();
if (ref != null) {
    // 对象已被回收,执行清理逻辑
}

这在缓存场景中很实用——你不用遍历所有引用检查 get() 是否为 null,直接从队列里拿被清理的引用就行。

面试高频追问

  1. ThreadLocal 为什么用弱引用?还会内存泄漏吗?

    ThreadLocalMap 的 Entry 的 key 是弱引用,当 ThreadLocal 对象没有外部强引用时,key 会被 GC 回收变为 null。但 value 仍然是强引用,如果不手动调用 remove(),value 就一直存在,造成内存泄漏。所以用完 ThreadLocal 一定要 remove(),这条规则我强调多少次都不嫌多。

  2. 软引用和弱引用在实际项目中怎么用?

    软引用常用于图片缓存、页面缓存等 "有了更好,没有也能工作" 的场景。弱引用常用于 WeakHashMap 做 Canonicalizing Map、监听器列表等。不过实际项目中,更推荐用成熟的缓存框架(Caffeine 支持软/弱引用策略),手写的容易出 bug。

  3. 虚引用有什么实际用途?一般开发用得到吗?

    日常业务开发基本用不到虚引用。它的主要使用者是 JDK 自身——比如 Cleaner 机制用来释放堆外内存,或者实现比 finalize() 更安全的资源清理方案。面试官问这个,主要是看你有没有研究过 JDK 底层机制。

常见面试变体

  • "为什么需要四种引用?只有强引用不行吗?"
  • "SoftReferenceWeakReference 的区别是什么?分别在什么场景下使用?"
  • "ThreadLocal 是如何防止内存泄漏的?"
  • "ReferenceQueue 有什么用?"

记忆口诀

强软弱虚,由强到弱

  • :宁死不屈,OOM 都不放手
  • :见机行事,内存不够才放手
  • :随 GC 去,一回收就放手
  • :形同虚设,只管通知不持有

总结

四种引用本质上是给开发者提供了 和 GC 协商对象生命周期 的能力。强引用完全掌控、软引用商量着来、弱引用主动让步、虚引用只当旁观者。面试中把每种引用的回收时机和典型场景说清楚,再带上 ThreadLocalCleaner 的例子,基本就是满分回答了。