一句话总结
Java 四种引用按强度递减:强引用(new 出来的,永不回收)→ 软引用(SoftReference,内存不足时才回收,适合做缓存)→ 弱引用(WeakReference,GC 时必定回收,ThreadLocal 的 key 就是弱引用)→ 虚引用(PhantomReference,无法通过 get() 获取对象,唯一用途是对象被回收时收到通知,用于管理直接内存)。配合 ReferenceQueue 可在对象被回收后执行清理操作。
初级理解
四种引用对比
| 引用类型 | 回收时机 | 用途 | get() 返回值 |
| 强引用 Strong | 永不回收(除非不可达) | 99% 的对象 | 对象本身 |
| 软引用 Soft | 内存不足时回收 | 内存敏感缓存 | 回收前返回对象,回收后返回 null |
| 弱引用 Weak | GC 时必定回收 | ThreadLocal、WeakHashMap | 回收前返回对象,回收后返回 null |
| 虚引用 Phantom | 任何时候都可能 | 管理直接内存(NIO) | 永远返回 null |
基本用法
// ① 强引用:最常见的引用
Object obj = new Object(); // obj 是强引用
obj = null; // 断开引用,对象可被 GC
// ② 软引用:内存不足时回收
SoftReference<byte[]> softRef = new SoftReference<>(new byte[1024*1024]);
byte[] data = softRef.get(); // 可能返回 null(已被回收)
// ③ 弱引用:GC 时必定回收
WeakReference<Object> weakRef = new WeakReference<>(new Object());
System.gc();
Object o = weakRef.get(); // 大概率返回 null
// ④ 虚引用:get() 永远返回 null,必须配合 ReferenceQueue
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);
中级深入
软引用做缓存
// 使用软引用实现内存敏感缓存
public class SoftCache<K, V> {
private final Map<K, SoftReference<V>> cache = new HashMap<>();
public V get(K key) {
SoftReference<V> ref = cache.get(key);
if (ref != null) {
V value = ref.get();
if (value != null) return value; // 缓存命中
cache.remove(key); // 已被回收,清理
}
return null;
}
public void put(K key, V value) {
cache.put(key, new SoftReference<>(value));
}
}
// 注意:软引用适合做"可有可无"的缓存
// 内存充足时保留,内存不足时自动释放
// 不适合做必须保留的核心数据缓存
WeakHashMap 原理
// WeakHashMap:key 是弱引用,key 被 GC 后 entry 自动移除
WeakHashMap<Object, String> map = new WeakHashMap<>();
Object key = new Object();
map.put(key, "value");
System.out.println(map.size()); // 1
key = null;
System.gc();
Thread.sleep(100); // 等待 GC 和 ReferenceQueue 处理
System.out.println(map.size()); // 0,entry 自动移除
// 与 HashMap 的区别:
// HashMap:key=null 后,entry 仍在,导致内存泄漏
// WeakHashMap:key 被 GC 后,entry 自动清理
ReferenceQueue 配合使用
// ReferenceQueue:引用对象被 GC 后,引用本身进入队列
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakReference<Object> ref = new WeakReference<>(new Object(), queue);
// 对象被 GC 后,ref 进入 queue
// 可以启动一个线程监控 queue,执行清理操作
while (true) {
Reference<?> r = queue.remove(); // 阻塞等待
// 执行清理操作(如释放直接内存)
}
高级拓展
虚引用的唯一用途:管理直接内存
// DirectByteBuffer 使用虚引用管理堆外内存
// 当 DirectByteBuffer 对象被 GC 时,通过虚引用通知清理堆外内存
// 简化原理:
class DirectByteBuffer {
private long address; // 堆外内存地址
DirectByteBuffer(int capacity) {
address = Unsafe.allocateMemory(capacity);
// 创建虚引用,关联到 ReferenceQueue
// 当 this 被 GC 时,虚引用进入 queue
// Cleaner 线程从 queue 取出引用,调用 Unsafe.freeMemory(address)
}
}
// 为什么用虚引用而不是 finalize()?
// 1. finalize() 执行时机不确定,可能导致堆外内存迟迟不释放
// 2. 虚引用 + ReferenceQueue 更可靠、更高效
ThreadLocal 为什么用弱引用?
// ThreadLocalMap 的 Entry 继承 WeakReference
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // value 是强引用!
}
// key(ThreadLocal)是弱引用:
// 当 ThreadLocal 对象没有外部强引用时,GC 可以回收 key
// 防止 ThreadLocal 对象无法回收导致的内存泄漏
// 但 value 是强引用,key 被回收后 value 仍在
// 所以必须调用 remove() 清理,否则 value 泄漏
// ThreadLocal 的 get/set/remove 方法会清理 key=null 的 entry
实战场景
场景:图片缓存用软引用
// 图片加载缓存:内存不足时自动释放
public class ImageCache {
private final Map<String, SoftReference<BufferedImage>> cache =
new ConcurrentHashMap<>();
public BufferedImage get(String path) {
SoftReference<BufferedImage> ref = cache.get(path);
if (ref != null) {
BufferedImage img = ref.get();
if (img != null) return img;
cache.remove(path);
}
BufferedImage img = loadImage(path);
cache.put(path, new SoftReference<>(img));
return img;
}
}
面试模拟
Q:四种引用的区别和使用场景?
A:强引用:new 出来的,永不回收,99% 场景;软引用:内存不足时回收,适合做缓存(如图片缓存);弱引用:GC 时必定回收,ThreadLocal 的 key、WeakHashMap;虚引用:get() 永远返回 null,唯一用途是对象回收时收到通知,用于管理直接内存(DirectByteBuffer)。
Q:软引用和弱引用的区别?
A:回收时机:软引用在内存不足时才回收,弱引用在 GC 时必定回收。用途:软引用适合做内存敏感缓存(内存够就保留),弱引用适合做"自动清理"的映射(如 WeakHashMap、ThreadLocal)。软引用比弱引用"强",对象能存活更久。
Q:虚引用有什么实际用途?
A:虚引用无法通过 get() 获取对象,唯一用途是对象被回收时收到通知。最典型的应用是 DirectByteBuffer:当 Java 对象被 GC 时,通过虚引用 + ReferenceQueue 通知 Cleaner 线程释放堆外内存。比 finalize() 更可靠高效。