【JVM】强、软、弱、虚引用

Java 的四种引用类型

Java 从 JDK 1.2 开始引入 java.lang.ref 包,提供了四种引用强度递减的引用类型,用于更灵活地控制对象的生命周期和垃圾回收行为。


一、强引用(Strong Reference)

最常见的引用方式,就是普通的对象赋值。

java 复制代码
Object obj = new Object();  // obj 就是强引用

特点:

  • 只要强引用还存在,GC 绝不会回收被引用的对象
  • 即使内存不足导致 OOM,也不会回收强引用对象
  • 强引用是造成内存泄漏的主要原因

内存泄漏示例:

java 复制代码
List<byte[]> list = new ArrayList<>();
while (true) {
    list.add(new byte[1024 * 1024]);  // 强引用累积,最终 OOM
}

打破强引用: 显式置空 obj = null,或让其超出作用域。


二、软引用(SoftReference)

用于描述一些"有用但非必需"的对象,适合做缓存。

java 复制代码
SoftReference<byte[]> softRef = new SoftReference<>(new byte[1024 * 1024]);
byte[] data = softRef.get();  // 获取对象,可能为 null

回收策略:

  • 内存充足时:不会被回收
  • 内存不足时(即将 OOM 前):GC 会回收软引用对象
  • 回收后 get() 返回 null

典型用途: 图片缓存、网页缓存等对内存敏感的场景。

java 复制代码
// 软引用缓存示例
Map<String, SoftReference<BufferedImage>> cache = new HashMap<>();

BufferedImage getImage(String path) {
    SoftReference<BufferedImage> ref = cache.get(path);
    BufferedImage img = (ref != null) ? ref.get() : null;
    if (img == null) {
        img = loadFromDisk(path);
        cache.put(path, new SoftReference<>(img));
    }
    return img;
}

⚠️ 注意:软引用可与 ReferenceQueue 配合,在对象被回收后收到通知。


三、弱引用(WeakReference)

比软引用更弱,只要发生 GC,无论内存是否充足都会被回收。

java 复制代码
WeakReference<Object> weakRef = new WeakReference<>(new Object());
Object obj = weakRef.get();  // GC 后可能为 null

特点:

  • 生命周期短暂,只能存活到下一次 GC 之前
  • 一旦 GC 发生,弱引用对象立即被回收

典型用途:

  • ThreadLocal 中的 ThreadLocalMap 的 key 使用弱引用
  • WeakHashMap(key 为弱引用,key 被回收后 entry 自动移除)
  • 避免内存泄漏的监听器/回调注册
java 复制代码
// WeakHashMap 示例:key 被 GC 后 entry 自动消失
WeakHashMap<Object, String> map = new WeakHashMap<>();
Object key = new Object();
map.put(key, "value");
key = null;          // 断开强引用
System.gc();         // 触发 GC
// 此时 map 中的 entry 会被移除

四、虚引用(PhantomReference)

最弱的引用,形同虚设。

java 复制代码
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object obj = new Object();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);
obj = null;

System.out.println(phantomRef.get());  // 永远返回 null

特点:

  • get() 永远返回 null,无法通过虚引用访问对象
  • 必须配合 ReferenceQueue 使用
  • 唯一作用:在对象被回收时收到一个系统通知,用于在对象销毁前做清理工作
  • 对象被回收的时机:虚引用入队时,对象实际上已不可达但尚未真正释放内存(可在此阶段做资源释放)

典型用途:

  • 管理堆外内存(如 DirectByteBuffer 的 Cleaner 机制就基于虚引用)
  • 替代 finalize() 做对象销毁前的清理
java 复制代码
// 虚引用 + 队列:对象被回收时收到通知
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
PhantomReference<byte[]> ref = new PhantomReference<>(new byte[1024], queue);

new Thread(() -> {
    try {
        Reference<? extends byte[]> r = queue.remove();
        System.out.println("对象即将被回收,做清理工作");
    } catch (InterruptedException e) { }
}).start();

五、四种引用对比

引用类型 类 GC 回收时机 get() 典型用途
强引用 普通赋值 永不回收(除非不可达) 对象本身 普通对象
软引用 SoftReference 内存不足时回收 可能为 null 内存敏感缓存
弱引用 WeakReference 下次 GC 时回收 可能为 null WeakHashMap、ThreadLocal
虚引用 PhantomReference 任何时候,配合队列 恒为 null 堆外内存清理、资源回收通知

六、引用队列(ReferenceQueue)

除强引用外,其余三种引用都可关联 ReferenceQueue。当引用指向的对象被 GC 回收后,引用对象本身会被加入队列,程序可通过轮询队列得知回收事件。

java 复制代码
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakReference<Object> ref = new WeakReference<>(new Object(), queue);
// GC 后,ref 会被放入 queue
Reference<?> r = queue.poll();  // 获取已回收的引用

关键点:入队的是引用对象(Reference),不是被引用的对象,因为后者已被回收。


七、补充:finalize() 与虚引用

早期 Java 用 finalize() 做对象销毁前清理,但存在性能差、执行时机不确定、可能导致对象"复活"等问题,已被标记废弃(JDK 9+)。虚引用 + Cleaner 是官方推荐的替代方案。

java 复制代码
// JDK 自带的 Cleaner(基于虚引用实现)
Cleaner cleaner = Cleaner.create();
cleaner.register(obj, () -> System.out.println("清理资源"));

总结一句话

强引用 保命,软引用 挤牙膏(内存不够才丢),弱引用 一碰 GC 就没,虚引用只负责"报丧"(通知回收)。

实际开发中,软引用/弱引用常用于缓存和避免内存泄漏,虚引用多用于底层资源管理,日常业务代码以强引用为主。

相关推荐
01二进制代码漫游日记6 小时前
[C++]之内存管理(一)
jvm·c++·学习
明天也要努力学习1 天前
深入理解 JVM 垃圾回收(GC)
java·jvm
铁链鞭策大师1 天前
jvm基础
jvm
香蕉大香蕉(06敬畏市场)2 天前
高频八股 · JVM(8)
java·jvm·面试·八股·大厂实习
北冥you鱼2 天前
Go 语言 GORM 使用 Preload:从关联查询到嵌套预加载的完整实践
开发语言·jvm·golang
兔头?2 天前
JVM学习文档
jvm·学习
wuminyu2 天前
JVM规范学习总结:class执行性能差异
java·linux·c语言·jvm·c++
EVEN-09132 天前
Linux-线程控制
linux·jvm
刚入门的大一新生3 天前
Linux-线程概念
linux·jvm