强引用、软引用、弱引用、虚引用的区别?

2025年 阅读约 8 分钟 面试指南 · Java面试 · JVM

深入解析Java四种引用类型:强引用(Strong)、软引用(SoftReference,内存不足时回收)、弱引用(WeakReference,GC时回收)、虚引用(PhantomReference,无法获取对象),附ReferenceQueue和面试模拟。

一句话总结

Java 四种引用按强度递减:强引用(new 出来的,永不回收)→ 软引用(SoftReference,内存不足时才回收,适合做缓存)→ 弱引用(WeakReference,GC 时必定回收,ThreadLocal 的 key 就是弱引用)→ 虚引用(PhantomReference,无法通过 get() 获取对象,唯一用途是对象被回收时收到通知,用于管理直接内存)。配合 ReferenceQueue 可在对象被回收后执行清理操作。

初级理解

四种引用对比

引用类型回收时机用途get() 返回值
强引用 Strong永不回收(除非不可达)99% 的对象对象本身
软引用 Soft内存不足时回收内存敏感缓存回收前返回对象,回收后返回 null
弱引用 WeakGC 时必定回收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() 更可靠高效。