TL;DR(30 秒扫完)
- 四种引用:强(默认)/ 软(内存敏感缓存)/ 弱(下次 GC 回收)/ 虚(对象死亡钩子)
- 回收优先级:虚 < 弱 < 软 < 强
- 典型场景:软→LRU 缓存;弱→ThreadLocal Key、WeakHashMap;虚→DirectByteBuffer Cleaner
- ThreadLocal 内存泄漏陷阱:Key 用弱引用可回收,但 Value 仍强引用,需手动
remove() PhantomReference.get()永远返回 null——它不是"引用对象",而是"监听对象死亡"
关键结论
结论 A四种引用强度对应四种回收时机(GC 触发 / OOM 前 / 永不自动 / 只通知)
结论 B软引用可用
new SoftReference<>(obj, maxCapacity) 精细控制结论 CThreadLocal 的 Value 是强引用,弱引用只解决 Key 泄漏,Value 仍需手动
remove()结论 D生产环境缓存建议用 Caffeine/Guava Cache,底层封装了软/弱引用 + ReferenceQueue
完整讲解(费曼四步)
STEP 1 · 概念
Java 引用强度分四级:强引用 > 软引用 > 弱引用 > 虚引用。强度越低,回收越激进。每种引用对应一种使用场景:默认对象、内存敏感缓存、辅助结构、对象死亡钩子。STEP 2 · 大白话
类比:把对象比作"员工",把 GC 比作"HR"。强引用像"编制内员工",HR 不主动裁;软引用像"外包员工",公司预算紧张就先裁;弱引用像"临时工",只要 HR 来巡视就裁;虚引用像"离职顾问",其实没工作,但会通知 HR 他离职了。
STEP 3 · 底层
四种引用对照
| 引用 | 类 | 回收时机 | 典型场景 | 关键点 |
|---|---|---|---|---|
| 强引用 | Object o = new Object() | 永不自动回收 | 常规对象 | 内存不足抛 OOM |
| 软引用 | SoftReference | 内存不足时回收,可配 maxCapacity | 图片缓存、Session 缓存 | new SoftReference<>(obj, maxCapacity) |
| 弱引用 | WeakReference | 下次 GC 就回收 | ThreadLocal Key、WeakHashMap | 单独用会被回收,需封装 |
| 虚引用 | PhantomReference | get() 永远 null | DirectByteBuffer 清理、对象死亡钩子 | 必须搭 ReferenceQueue |
ThreadLocal 内存泄漏的真相
ThreadLocal 内部 ThreadLocalMap 的结构:// 简化后的 ThreadLocalMap 结构
Entry extends WeakReference<ThreadLocal<?>> {
Object value; // Value 是强引用!
}
- Key 是 WeakReference:线程不死时让 Key 可回收(避免 ThreadLocal 对象本身泄漏)
- Value 是强引用:Key 被回收后,Value 依然占用内存 → "幽灵键"泄漏
- 解决:手动
try-finally包裹 +ThreadLocal.remove()
threadLocal.set(value);
try {
// 使用
} finally {
threadLocal.remove(); // 关键!
}
PhantomReference 的正确用法
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<>(obj, queue);
// phantom.get() 永远返回 null
// 对象被 GC 时,phantom 会被加入 queue
// 消费 queue 得到"对象死亡通知"
// 典型场景:DirectByteBuffer Cleaner 内部实现
WeakHashMap 使用陷阱
- WeakHashMap 的 Key 用弱引用,Value 是强引用
- 单独使用仍可能泄漏(Value 一直持有)
- 依赖 GC 触发时机,不能保证立即释放
STEP 4 · 简化
一句话总结:四种引用 = 四种回收时机。虚<弱<软<强。ThreadLocal 用弱引用做 Key 但 Value 强引用,必须手动 remove()。生产环境缓存用 Caffeine/Guava。
记忆口诀:- 强:常规对象,永不 GC
- 软:内存紧张才 GC(软引用 = 内存缓存)
- 弱:下次 GC 就 GC(弱引用 = ThreadLocal Key)
- 虚:只收通知,get 永远 null(虚引用 = DirectByteBuffer 清理)
常见误区
"弱引用会优先回收"
弱引用下次 GC 就回收,时机不由开发者决定;"优先"是错觉
"ThreadLocal 用弱引用就不会泄漏"
Key 弱引用可回收,Value 仍强引用,需手动
remove()"PhantomReference 是用来引用对象的"
get() 永远返回 null,本质是"对象死亡钩子""虚引用和弱引用差不多"
虚引用只通知不持有,弱引用可
get() 拿到对象"软引用 = 强引用"
软引用受
-XX:SoftRefLRUPolicyMSPerMB 影响存活时长,可配 maxCapacity延伸追问
ThreadLocal 为什么用弱引用?为什么还会泄漏?
Key 用 WeakReference 让 Key 可回收;但 Value 仍强引用,若线程池线程不死,Key 被回收后变成"幽灵键",Value 依然占用内存;解决靠手动
remove()软引用如何做精细的 LRU 缓存?
设置 SoftReference 的 maxCapacity,配合 ReferenceQueue 主动清理已回收的引用;生产更推荐 Caffeine/Guava Cache
DirectByteBuffer 泄漏如何排查?
JDK 8 用 PhantomReference + ReferenceQueue;JDK 9+ 用 Cleaner;监控
jcmd VM.native_memory、JFR jdk.DirectByteBuffer、-XX:MaxDirectMemorySizeWeakHashMap 会避免泄漏吗?
单独看不能(值仍强引用),配合 GC 触发时机;不能保证立即释放
Caffeine 内部用了哪些引用策略?
weakKeys / weakValues / softValues / expireAfterAccess / expireAfterWrite 组合;配合 ReferenceQueue 主动清理
速查表
四种引用: 强(默认)> 软(缓存)> 弱(下次GC)> 虚(死亡钩子)
回收优先级: 虚 < 弱 < 软 < 强
典型场景: 软→LRU / 弱→ThreadLocal Key / 虚→DirectByteBuffer
ThreadLocal 泄漏: Key 弱引用 / Value 强引用 / 手动 remove()
生产缓存: Caffeine 或 Guava Cache(不必裸用引用类型)
Anki 候选卡片
Q: 四种引用回收优先级?
A: 虚 < 弱 < 软 < 强
Q: PhantomReference.get() 返回什么?
A: 永远 null,只能搭配 ReferenceQueue 使用
Q: ThreadLocal 内部 Key 用哪种引用?
A: WeakReference(Key 可回收),但 Value 仍强引用
Q: ThreadLocal 内存泄漏怎么解?
A: 手动
remove(),或 try-finally 包裹Q: SoftReference 怎么设容量?
A:
new SoftReference<>(obj, maxCapacity)关联题目
关联知识
四种引用强度对应四种回收时机:强(默认)> 软(OOM 前)> 弱(下次 GC)> 虚(对象死亡钩子)
对象像员工,GC 像 HR:强=编制内、软=外包(预算紧先裁)、弱=临时工(巡视就裁)、虚=离职顾问(没工作但会通知)
✦ 记 忆 口 诀 ✦
回收优先级:虚<弱<软<强;ThreadLocal Value 是强引用,弱引用只解决 Key 泄漏
关键可视化
四种引用对照
flowchart TB R["引用强度"] --> S["强引用<br/>Object o = new Object()<br/>永不自动回收<br/>常规对象<br/>OOM 时抛 OutOfMemory"] R --> Soft["软引用<br/>SoftReference<T><br/>内存不足时回收<br/>图片缓存、Session 缓存<br/>可配 maxCapacity"] R --> Weak["弱引用<br/>WeakReference<T><br/>下次 GC 就回收<br/>ThreadLocal Key、WeakHashMap<br/>单独用会被回收"] R --> Phantom["虚引用<br/>PhantomReference<T><br/>get() 永远 null<br/>DirectByteBuffer 清理<br/>对象死亡钩子"]
ThreadLocal 内存泄漏的真相
flowchart LR T["ThreadLocalMap"] --> E["Entry extends WeakReference<ThreadLocal<?>>"] E --> K["Key = WeakReference<br/>线程不死时让 Key 可回收<br/>避免 ThreadLocal 对象本身泄漏"] E --> V["Value = 强引用<br/>Key 被回收后 Value 依然占内存<br/>幽灵键泄漏"] V -.解决.-> F["try-finally 包裹<br/>ThreadLocal.remove()"]
ReferenceQueue 通知机制
flowchart LR R["引用对象"] --> Q["ReferenceQueue"] Q --> P["PhantomReference 入队<br/>通知对象死亡"] Q --> C["Cleanup 线程处理<br/>释放堆外内存"] P -.-> D["DirectByteBuffer 释放<br/>Cleaner 实现"]
知识关系
⬆️ 前置(Prerequisite)
jvm-memory-model🔄 延伸(Extends)
暂无
🎯 概念
📏 规则
⚠️ 误区
🔍 追问
✨ 口诀
共 0 张卡,点击翻面