☰
Java 强弱软虚引用详解
2026/10/9 2:53:59 网站建设 项目流程

Java 强弱软虚引用详解

前提:Java 引用体系,目的是在 GC 回收对象时,对对象存活规则做精细化控制,都在java.lang.ref包。 除了强引用,剩下 3 种(软、弱、虚)都属于可被 GC 回收的引用,配套ReferenceQueue引用队列,在对象被回收时收到通知。

1. 强引用 Strong Reference(默认)

定义:普通对象引用,只要引用链存在,GC永远不会回收。

// 强引用 Object obj = new Object();
  • 回收条件:断开引用obj = null;,之后没有任何强引用指向该对象,GC 才可能回收。
  • 特点:最常用,容易造成内存泄漏(集合里长期持有对象引用)。 ✅适用场景绝大多数业务代码:普通对象、成员变量、局部变量。只要你希望对象在引用存在时,一定不被回收。

❌ 风险场景:长生命周期集合缓存,持续放入大对象,不手动清理 → OOM。

集合长期持有对象引用导致内存泄漏(Java 场景)

核心原理:

集合是强引用,只要集合本身还存活,集合里面的元素对象就不会被 GC 回收;本该释放的对象一直被集合引用挂住,无法回收 → 内存泄漏。

前提:Java 默认ArrayList、HashMap、HashSet都是强引用容器。

场景 1:静态集合(最经典)

静态变量生命周期 = 整个 JVM,静态集合一旦放入对象,只要不清掉,对象永远无法 GC。

public class MemoryLeakDemo { // static 全局集合,JVM退出才销毁 private static final List<Object> CACHE_LIST = new ArrayList<>(); public void addData() { Object data = new Object(); CACHE_LIST.add(data); // 业务认为data用完了,方法结束,局部变量data引用消失 // 但是!CACHE_LIST还持有data的强引用,data不会被GC // 如果不断add,对象持续堆积,OOM } // 忘记写移除逻辑!! }

问题:对象业务上已经不用了,但静态 List 还保留引用,GC 无法回收。 ✅ 修复:用完CACHE_LIST.remove(obj)/clear();或者用弱引用容器WeakHashMap。

场景 2:HashMap 存对象,key 不再使用但没 remove

Map<User, String> userMap = new HashMap<>(); User user1 = new User(1,"张三"); userMap.put(user1, "测试"); user1 = null; // 局部引用置空 // ❗ HashMap内部table数组仍然持有user1对象强引用!对象不会GC

很多人误以为user1=null就可以回收,map 里的引用还在,对象存活。

补充:WeakHashMap的 key 是弱引用,key 对象没有外部强引用时,会自动被 GC,适合缓存场景。

场景 3:ArrayList 扩容后,数组残留过期引用(容易被忽略)

ArrayList 底层是Object[] elementData数组。 调用remove()时:

  • 如果不是末尾元素,会System.arraycopy前移;
  • 最后一个空位的引用不会自动置为 null,数组依然持有该对象引用。
ArrayList<String> list = new ArrayList<>(10); list.add("A"); list.add("B"); list.add("C"); list.remove(1); // 删除B // 底层数组:["A","C", "B", null...] // 原来B位置被C覆盖,但数组末尾的空位,还残留旧对象引用

源码注释也提到这个点:需要手动置 null 帮助 GC。 短生命周期 list 影响不大;长生命周期的 ArrayList,大量 remove 不清理,就会泄漏。

场景 4:集合作为类成员变量,外部不断 add,但是没有清理机制

长存活对象(单例、Spring Bean)里面持有集合成员:

@Component public class MessageManager { // Bean单例,生命周期容器级别 private List<Message> msgList = new ArrayList<>(); public void addMsg(Message msg){ msgList.add(msg); } // 业务消息消费完成后,忘记从msgList移除! // msg对象一直被msgList持有,堆积内存泄漏 }

业务上消息已经处理完毕,但是单例 Bean 的集合一直保留 Message 对象引用。

场景 5:线程池 + 集合,线程长期存活,集合引用对象

线程池核心线程不会销毁,线程内部的集合如果持续 add 对象,不清理:

ExecutorService pool = Executors.newSingleThreadExecutor(); pool.submit(() -> { List<BigData> list = new ArrayList<>(); while(true){ BigData data = readData(); list.add(data); // 处理完data,没有remove } });

线程常驻,list 一直持有所有 BigData 对象引用,持续涨内存。


2. 软引用 SoftReference

定义:内存充足时不回收;内存不足(即将 OOM)时,GC 会回收软引用指向的对象。

一句话:内存够就留着,内存不够就清掉。

import java.lang.ref.SoftReference; public class SoftDemo { public static void main(String[] args) { Object data = new Object(); // 软引用包装对象 SoftReference<Object> softRef = new SoftReference<>(data); // 断开强引用,只剩下软引用 data = null; // 获取对象,可能拿到,也可能已经被回收返回null Object get = softRef.get(); if (get != null) { System.out.println("对象还在"); } else { System.out.println("内存不足,对象已被GC回收"); } } }

✅适用场景:内存敏感缓存(图片缓存、页面临时数据缓存)

缓存希望尽量复用,但是内存紧张时允许自动丢掉缓存,防止 OOM。

注意:JVM 不保证一定会回收,只是在要 OOM 的时候优先回收软引用对象。


3. 弱引用 WeakReference

定义:只要发生 GC,不管内存够不够,只要只剩弱引用,直接回收。

一句话:一次 GC 就没了。

import java.lang.ref.WeakReference; public class WeakDemo { public static void main(String[] args) { Object data = new Object(); WeakReference<Object> weakRef = new WeakReference<>(data); data = null; // 切断强引用 System.gc(); // 手动触发GC(仅建议演示,生产不依赖) Object get = weakRef.get(); System.out.println(get); // null,对象被回收 } }

✅经典场景:

WeakHashMapkey 是弱引用。

当 key 对象没有其他强引用,GC 自动回收 key,map 自动移除这条 entry。

典型用途:防止内存泄漏,比如监听器、临时元数据、ThreadLocal 原理相关的弱引用。

经典坑:ThreadLocalMap的 Entry key 是弱引用,但 value 是强引用,如果不 remove,value 依然内存泄漏。


4. 虚引用 PhantomReference

定义:最弱引用,不能通过 get 拿到对象,get 永远返回 null。

唯一作用:对象被回收时收到一个通知。

虚引用本身不维持对象存活,仅用来追踪对象被 GC 清理的时机,必须配合ReferenceQueue使用。

import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; public class PhantomDemo { public static void main(String[] args) throws InterruptedException { ReferenceQueue<Object> queue = new ReferenceQueue<>(); Object data = new Object(); // 虚引用,绑定引用队列 PhantomReference<Object> phantomRef = new PhantomReference<>(data, queue); data = null; System.gc(); // 从队列取出,代表对象已经被回收 java.lang.ref.Reference<?> ref = queue.remove(); System.out.println("对象已进入回收阶段:" + ref); System.out.println(phantomRef.get()); // null,永远拿不到对象 } }

✅适用场景:堆外内存管理、资源释放

直接内存(DirectBuffer)清理、NIO 的 Cleaner 底层就是虚引用。

不能在虚引用回调里操作原对象,因为对象已经不可访问,只能做后置资源清理(释放堆外内存、关闭底层资源)。

对比汇总表

引用类型GC 回收时机get()核心用途
强引用引用置 null 之后,GC 才回收返回对象普通业务对象,默认
软引用内存不足,即将 OOM 时回收可能返回对象,可能 null内存友好缓存
弱引用只要 GC 运行就回收可能返回对象,可能 null防止内存泄漏,WeakHashMap
虚引用对象 finalize 后,准备回收时入队永远返回 null追踪 GC 回收事件,堆外内存清理

概括

  • 强引用:活着就不回收
  • 软引用:内存不够才回收
  • 弱引用:只要 GC 就回收
  • 虚引用:拿不到对象,只通知回收

扩展

  1. finalize()和虚引用区别:finalize 不可靠,可能不执行;虚引用是更稳定的回收通知机制,现在推荐用虚引用替代 finalize。
  2. 引用队列:软 / 弱 / 虚都可以绑定队列,对象被回收后,reference 对象会被丢进队列,业务可以轮询队列感知回收事件。
  3. 实际开发:业务代码极少手动写 Soft/Phantom,WeakHashMap 偶尔会用到;NIO Cleaner 是底层虚引用典型案例。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询