【JVM原理详解】23-四种引用类型-强软弱虚
23-四种引用类型强、软、弱、虚前两篇讨论的 GC Roots 和可达性分析本质是在回答对象是否被引用。但现实中我们常常希望对引用做更精细的控制比如缓存里的对象内存够用就保留、不够再回收又比如有些资源需要在对象被回收前执行一段清理逻辑。Java 提供了四种引用类型来满足这些需求——StrongReference、SoftReference、WeakReference、PhantomReference。本篇将逐一剖析它们的语义、实现与典型应用。引用强度的设计意图JDK 1.2 之前Java 只有引用这一种概念——只要引用存在对象就永不回收。这对缓存、临时映射等场景过于刚性。于是在 1.2 引入了java.lang.ref包将引用划分为四个级别从强到弱依次为强引用 (Strong) 软引用 (Soft) 弱引用 (Weak) 虚引用 (Phantom)引用越弱对象被回收的优先级越高。这套机制让开发者可以在对象可达性之外再叠加一层回收优先级的语义。StrongReference永不主动回收StrongReference 就是我们日常写的普通引用ObjectobjnewObject();// 强引用只要强引用还存在垃圾收集器永远不会回收被引用的对象——哪怕 OOM 也不会。这是 JVM 规范的硬性保证也是其他三种引用的对比基准。工程上绝大多数内存泄漏都源于本应是临时引用的强引用被意外长期持有比如publicclassLeakyCache{// 强引用持有永不回收privatestaticfinalMapString,byte[]CACHEnewHashMap();publicstaticvoidput(Stringkey,byte[]data){CACHE.put(key,data);}// 没有 remove 方法 → 内存泄漏}避免强引用泄漏的核心是显式置 null或用更弱的引用替代。SoftReference内存不足时回收SoftReference 描述还有用但非必需的对象。在 JVM 抛出 OOM 之前会清理软引用对象清理后仍内存不足才会真正 OOM。典型应用缓存importjava.lang.ref.SoftReference;importjava.util.HashMap;importjava.util.Map;/** * 基于软引用的简单缓存 * 适用 JDK 8/11/17 */publicclassSoftCacheK,V{privatefinalMapK,SoftReferenceVcachenewHashMap();publicvoidput(Kkey,Vvalue){cache.put(key,newSoftReference(value));}publicVget(Kkey){SoftReferenceVrefcache.get(key);returnrefnull?null:ref.get();}}SoftReference.get()在对象被回收后返回null调用方需要处理这种缓存未命中的情况。回收时机HotSpot 对软引用的回收遵循最近最少使用原则最近被访问过的软引用会保留更久。这是通过-XX:SoftRefLRUPolicyMSPerMB默认 1000ms参数控制的堆中每剩余 1MB 空间软引用的存活时间增加约 1 秒。# 调大软引用存活时长每 MB 多活 2 秒java-XX:SoftRefLRUPolicyMSPerMB2000-cpMyApp com.example.Main需要注意G1 之后默认开启-XX:UseG1GC软引用回收策略略有变化G1 通过-XX:G1SoftRefLRUPolicyMSPerMB单独控制。SoftReference 的代价软引用对象本身SoftReference实例是强引用它内部通过一个referent字段弱指向真正的对象。GC 在回收 referent 前会先检查软引用条件满足则把 referent 字段置 null、并把 SoftReference 实例放入关联的 ReferenceQueue。这意味着软引用本身仍占内存需要配合队列清理。WeakReference下次 GC 即回收WeakReference 比 SoftReference 更弱——只要发生 GC无论内存是否充足弱引用指向的对象都会被回收前提是没有其他强引用。典型应用WeakHashMapimportjava.util.WeakHashMap;classUserHolder{privatestaticfinalWeakHashMapObject,StringMETAnewWeakHashMap();publicstaticvoidbind(Objectkey,Stringtag){META.put(key,tag);}// 当 key 对象在外部失去引用后下次 GC 会自动清理该 entry}WeakHashMap的 key 是弱引用value 是强引用。这种key 弱、value 强的结构有一个潜在陷阱value 本身可能反向引用 key导致 key 实际上无法回收。这类问题排查难度较大建议 value 持有的对 key 的引用尽量用弱引用。ThreadLocal 的实现ThreadLocal内部也是用WeakReference持有 ThreadLocal 实例本身// ThreadLocal.ThreadLocalMap.Entry 源码片段staticclassEntryextendsWeakReferenceThreadLocal?{Objectvalue;Entry(ThreadLocal?k,Objectv){super(k);// key 是弱引用valuev;// value 是强引用}}这就解释了为什么 ThreadLocal 实例本身可以被回收但value 仍可能泄漏value 强引用了真实数据只要线程不死value 就不会被回收。所以使用 ThreadLocal 必须remove()。PhantomReference对象回收前通知PhantomReference 是最弱的一种通过它甚至无法获取到对象本身——get()永远返回null。它的唯一作用是在对象被回收时收到通知常用于资源清理。ReferenceQueue 工作原理四种引用都可以在创建时关联一个ReferenceQueue。GC 回收 referent 后会把对应的 Reference 对象入队enqueue。应用线程从队列中取出 Reference就能知道哪个对象被回收了。创建引用 GC 回收 referent Reference Queue ────────────────────── Reference 入队 | 应用线程 poll → 执行清理虚引用必须配合 ReferenceQueue 使用否则毫无意义——因为它的get()永远是 null没有队列就没法感知回收事件。Cleaner 机制JDK 9 引入了java.lang.ref.Cleaner它本质上是一个虚引用 ReferenceQueue 的封装替代了已过时的finalize()。importjava.lang.ref.Cleaner;/** * 使用 Cleaner 管理本地资源 * 适用 JDK 9 */publicclassNativeResourceimplementsAutoCloseable{privatestaticfinalCleanerCLEANERCleaner.create();privatestaticclassStateimplementsRunnable{privatelongnativeHandle;// 模拟本地句柄State(longhandle){this.nativeHandlehandle;}Overridepublicvoidrun(){// 对象被回收时回调if(nativeHandle!0){freeNative(nativeHandle);nativeHandle0;}}}privatefinalStatestate;privatefinalCleaner.Cleanablecleanable;publicNativeResource(longhandle){this.statenewState(handle);this.cleanableCLEANER.register(this,state);}Overridepublicvoidclose(){cleanable.clean();// 主动清理}privatestaticnativevoidfreeNative(longhandle);}关键点State不持有外部对象NativeResource的引用避免阻止其回收。Cleaner.register(this, state)内部把this用虚引用包装、关联到 Cleaner 自己的队列。NativeResource被回收时Cleaner 线程会执行state.run()完成清理。也可以主动调用cleanable.clean()提前清理幂等。PhantomReference vs finalizefinalize()在 JDK 9 被标记 deprecatedJDK 18 起默认禁用。它的核心问题是可以复活对象、执行时机不确定、执行线程无保证。Cleaner 通过独立的清理线程和不可访问 referent的设计规避了这些问题。完整对比示例下面用一段代码同时演示四种引用在 GC 前后的行为差异importjava.lang.ref.*;importjava.util.ArrayList;importjava.util.List;publicclassReferenceDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{ReferenceQueueObjectqueuenewReferenceQueue();// 1. 强引用ObjectstrongnewObject();// 2. 软引用SoftReferenceObjectsoftnewSoftReference(newObject(),queue);// 3. 弱引用WeakReferenceObjectweaknewWeakReference(newObject(),queue);// 4. 虚引用PhantomReferenceObjectphantomnewPhantomReference(newObject(),queue);// 检查引用是否还在System.out.println(Before GC:);System.out.println( strong (strong!null));System.out.println( soft (soft.get()!null));System.out.println( weak (weak.get()!null));System.out.println( phantom (phantom.get()!null));// 总是 null// 触发 GCSystem.gc();Thread.sleep(500);System.out.println(After GC:);System.out.println( strong (strong!null));System.out.println( soft (soft.get()!null));// 内存够时仍存活System.out.println( weak (weak.get()!null));// 通常被回收System.out.println( phantom (phantom.get()!null));// null// 从队列中取出已回收的引用Reference?ref;while((refqueue.poll())!null){System.out.println( 回收通知: ref.getClass().getSimpleName());}}}典型输出内存充足时Before GC: strong true soft true weak true phantom false After GC: strong true soft true weak false phantom false 回收通知: WeakReference 回收通知: PhantomReference软引用在内存充足时不会被回收只有在 OOM 前才被清理弱引用一遇 GC 即清虚引用的 referent 被回收后会触发入队。实践要点1. 缓存优先用 Caffeine / Guava虽然 SoftReference 可以做缓存但回收时机依赖 GC 策略不便于容量规划。生产场景更推荐 Caffeine它支持weakKeys()/weakValues()/softValues()配置并自带 LRU/LFU 策略可控性远高于手工实现。2. WeakHashMap 不是并发安全的WeakHashMap没有同步机制并发场景需用Collections.synchronizedMap包裹或改用ConcurrentHashMapWeakReference自行实现。3. ReferenceQueue 必须主动消费如果关联了队列但从不poll队列中的 Reference 对象会一直占用内存——这是隐性泄漏。Cleaner 内部已经处理了这个问题所以优先用 Cleaner。4. 虚引用的 referent 在入队前已被回收注意JDK 实现中PhantomReference 是在 referent 被** finalize 后、内存释放前**入队的。这意味着此时 referent 已无法访问但仍占用内存直到 Cleaner 执行完。所以虚引用不能用于复活对象。5. 软引用在 G1/ZGC 下的行为G1 对软引用采用分区回收策略可能延迟回收部分软引用ZGC 则在并发标记阶段处理。生产中如果发现软引用占用偏高可调整SoftRefLRUPolicyMSPerMB或改用显式 TTL 缓存。小结强引用永不回收是日常引用的默认形态也是泄漏主因。软引用在 OOM 前回收适合内存敏感的缓存存活时长由SoftRefLRUPolicyMSPerMB控制。弱引用下次 GC 即回收WeakHashMap与ThreadLocal都基于它。虚引用无法获取对象仅在对象回收前通过 ReferenceQueue 通知配合Cleaner替代finalize()。ReferenceQueue 是引用与回收通知的桥梁必须主动消费。下一篇我们将从判定对象存活过渡到如何回收深入对比四种经典垃圾回收算法。更多内容JVM调优实战

相关新闻

【JVM原理详解】22-GC-Roots详解

【JVM原理详解】22-GC-Roots详解

22-GC Roots 详解 上一篇讲到,可达性分析从一组"确定存活"的根对象出发,去遍历整张对象图。这组根对象就是 GC Roots。但到底哪些对象能成为 GC Roots?这并不是一句"栈里的引用"就能说清楚的——HotSpot 实际定义了多类…

2026/11/4 6:35:30 阅读更多 →
ClearerVoice-Studio语音处理引擎架构深度解析:多模态融合与实时噪声抑制技术实现

ClearerVoice-Studio语音处理引擎架构深度解析:多模态融合与实时噪声抑制技术实现

ClearerVoice-Studio语音处理引擎架构深度解析:多模态融合与实时噪声抑制技术实现 【免费下载链接】ClearerVoice-Studio An AI-Powered Speech Processing Toolkit and Open Source SOTA Pretrained Models, Supporting Speech Enhancement, Separation, and Targe…

2026/10/22 3:25:02 阅读更多 →
Kimi 长文本稳赢、豆包搜索翻车?国产三强在 Taotoken 平台的 4 维实测报告

Kimi 长文本稳赢、豆包搜索翻车?国产三强在 Taotoken 平台的 4 维实测报告

三大模型实战横评:Kimi、豆包、通义千问在Taotoken平台的技术角力 上个月用Taotoken同时接入Kimi、豆包和通义千问API进行深度测试时,我们意外发现三家大模型的技术特性差异远超预期——有的模型在专业领域表现惊艳却在基础功能上漏洞百出。经过72小时高…

2026/11/4 12:16:01 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/4 1:35:24 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/3 10:57:27 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/3 11:35:49 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/4 6:23:50 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/3 10:47:27 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/3 10:57:33 阅读更多 →

日新闻

周新闻

月新闻