基础篇七 你的对象被 GC 回收了还是不敢回收?四种引用强度决定它的命运

张开发
2026/5/4 4:38:00 15 分钟阅读
基础篇七 你的对象被 GC 回收了还是不敢回收?四种引用强度决定它的命运
文章目录一、全貌四种引用强度一览二、强引用最硬的关系宁可 OOM 也不放手三、软引用内存紧张时的备胎软引用最适合做缓存四、弱引用GC 一来就没了弱引用最经典的场景WeakHashMapThreadLocal 为什么会内存泄漏五、虚引用最神秘的引用拿不到对象虚引用 ReferenceQueue 回收通知虚引用的典型用途管理堆外内存六、四种引用对比一张图刻进脑子七、引用队列 ReferenceQueue引用的收件箱八、面试速答模板个人网站你有没有想过一个问题JVM 怎么决定一个对象该不该回收答案很简单——看还有没有人在引用它。但引用这件事远不止有和没有两种状态。Java 设计了四种引用强度就像四个等级的护身符有的让对象坚不可摧有的让对象随时可能消失有的甚至在对象消失后还能通知你一声。搞懂这四种引用你就拿到了理解 GC 和内存缓存的钥匙。一、全貌四种引用强度一览从强到弱排列引用类型护身符等级GC 时的态度用途强引用 Strong️ 钢铁护盾宁可 OOM 也不回收日常开发默认软引用 Soft 防风外套内存不够了才回收缓存弱引用 Weak 纸糊围裙下次 GC 就回收避免内存泄漏虚引用 Phantom 隐身衣随时回收拿不到对象跟踪回收、管理堆外内存核心规律引用越弱GC 越不留情。用一个生活类比串起来强引用 你爸妈——只要他们在你的生活费永远不会断软引用 公司年终奖——效益好的时候有效益差了就没了弱引用 顺风车——有车就搭车走了你就得下来虚引用 遗嘱执行人——人都不在了只负责处理后事二、强引用最硬的关系宁可 OOM 也不放手强引用就是我们每天写的普通赋值ObjectobjnewObject();// 这就是强引用只要obj还指向那个对象GC 绝对不敢动它——哪怕内存快撑爆了JVM 宁可抛出OutOfMemoryError也不会回收强引用对象。// 模拟强引用导致 OOMListObjectlistnewArrayList();while(true){list.add(newObject());// 强引用GC 不敢回收// 最终 → java.lang.OutOfMemoryError: Java heap space}怎么让强引用对象可被回收手动断开引用objnull;// 断开强引用GC 下次巡视时就可以回收了这也是为什么在使用完大对象后手动置 null 是一种优化手段——但大多数情况下让变量自然离开作用域就行。三、软引用内存紧张时的备胎软引用用SoftReference包装 GC 的态度是内存够用就留着不够就回收。importjava.lang.ref.SoftReference;SoftReferencebyte[]softnewSoftReference(newbyte[1024*1024*10]);// 获取对象byte[]datasoft.get();if(data!null){// 对象还在正常使用}else{// 对象已被 GC 回收需要重新创建}软引用最适合做缓存想象你在做图片缓存图片加载到内存里希望尽量复用但内存不够时可以自动释放——这就是软引用的绝佳场景。// 图片缓存示例MapString,SoftReferenceImageimageCachenewHashMap();voidloadImage(Stringurl){SoftReferenceImagerefimageCache.get(url);Imageimg(ref!null)?ref.get():null;if(img!null){// 缓存命中直接用returnimg;}// 缓存未命中或已被回收重新加载imgloadFromDisk(url);imageCache.put(url,newSoftReference(img));returnimg;}内存充裕时缓存一直在性能飞起内存紧张时GC 自动回收软引用对象不会 OOM这就是 Guava Cache、Ehcache 等缓存框架底层会用到的思想。当然它们实现更复杂但核心逻辑一样——用软引用在性能和内存安全之间找平衡。四、弱引用GC 一来就没了弱引用用WeakReference包装比软引用更脆弱——不管内存够不够下次 GC 就回收。importjava.lang.ref.WeakReference;WeakReferencebyte[]weaknewWeakReference(newbyte[1024*1024*10]);System.out.println(weak.get());// [BxxxxxSystem.gc();// 建议 GC 执行System.out.println(weak.get());// null被回收了弱引用最经典的场景WeakHashMapimportjava.util.WeakHashMap;WeakHashMapKey,ValuemapnewWeakHashMap();KeykeynewKey(myKey);map.put(key,newValue(myValue));System.out.println(map.get(key));// myValuekeynull;// 断开强引用System.gc();System.out.println(map.size());// 0自动清理了普通 HashMap 的问题如果你把 key 的强引用断开了但 Map 还持有 key 的强引用这个 Entry 永远不会被回收——内存泄漏WeakHashMap 的 key 是弱引用key 被回收后Entry 也会自动清除不会内存泄漏。ThreadLocal 为什么会内存泄漏ThreadLocal 的内部实现用了弱引用但仍有泄漏风险Thread → ThreadLocalMap → Entry(key: WeakReferenceThreadLocal, value: 强引用)key 是弱引用 → ThreadLocal 对象被回收后key 变为 null ✅value 是强引用 → 但 key 为 null 的 Entryvalue 还在❌所以 ThreadLocal 每次用完要调remove()否则 value 可能泄漏。五、虚引用最神秘的引用拿不到对象虚引用用PhantomReference包装它有一个最反直觉的特点get() 永远返回 null。importjava.lang.ref.PhantomReference;importjava.lang.ref.ReferenceQueue;ObjectobjnewObject();ReferenceQueueObjectqueuenewReferenceQueue();PhantomReferenceObjectphantomnewPhantomReference(obj,queue);System.out.println(phantom.get());// null永远拿不到你可能会问拿不到对象这引用有什么用答案是它不拿对象它只关心对象什么时候死的。虚引用 ReferenceQueue 回收通知虚引用必须配合ReferenceQueue使用。当对象被回收后虚引用会被加入队列ObjectobjnewObject();ReferenceQueueObjectqueuenewReferenceQueue();PhantomReferenceObjectphantomnewPhantomReference(obj,queue);objnull;// 断开强引用System.gc();// 检查队列Reference?refqueue.poll();if(ref!null){System.out.println(对象已被回收可以清理相关资源了);}虚引用的典型用途管理堆外内存DirectByteBuffer分配的是堆外内存GC 只能回收堆内对象堆外内存管不了。虚引用可以在对象回收时通知你让你手动释放堆外内存——这就是Cleaner机制的原理// JDK 9 Cleaner底层就是虚引用CleanercleanerCleaner.create();cleaner.register(obj,()-{// 对象被回收后执行清理逻辑freeDirectMemory();});六、四种引用对比一张图刻进脑子引用强度强 ──────────────────────────→ 弱 GC 态度绝不回收 → 快没内存才回收 → 下次就回收 → 已经回收了 强引用Object obj new Object() └→ obj 还在→ 不回收OOM 也不回收 软引用SoftReferencebyte[] soft new SoftReference(data) └→ 内存够→ 留着 内存不够→ 回收 弱引用WeakReferencebyte[] weak new WeakReference(data) └→ GC 来了→ 回收不管内存够不够 虚引用PhantomReferenceObject phantom new PhantomReference(obj, queue) └→ 对象已回收 → 通知我一声get() 永远 null七、引用队列 ReferenceQueue引用的收件箱软引用和弱引用也可以配合 ReferenceQueue 使用——当引用对象被回收后Reference 对象会被加入队列ReferenceQueuebyte[]queuenewReferenceQueue();SoftReferencebyte[]softnewSoftReference(newbyte[1024],queue);// ... GC 发生后 ...Reference?extendsbyte[]refqueue.poll();if(ref!null){// soft 指向的对象已被回收可以清理 SoftReference 本身}这个机制在缓存框架中很常用清理那些 value 已被回收但 SoftReference 对象还留在 Map 里的空壳。八、面试速答模板QJava 四种引用的区别A强引用最硬只要引用存在 GC 就不回收宁可 OOM软引用在内存不足时才会被回收适合做缓存弱引用更弱下次 GC 就回收适合避免内存泄漏虚引用最弱get() 永远返回 null只能配合 ReferenceQueue 在对象回收后收到通知用于管理堆外内存等资源清理。Q软引用和弱引用的区别A软引用是内存不够才回收弱引用是GC 来了就回收。软引用适合做内存敏感的缓存如图片缓存弱引用适合做自动清理的映射如 WeakHashMap。两者都可以配合 ReferenceQueue 使用在对象被回收后收到通知。Q虚引用有什么用既然 get() 永远是 nullA虚引用的目的不是访问对象而是跟踪对象何时被回收。它必须配合 ReferenceQueue当对象被回收后虚引用会入队你可以在队列中检测到这一事件并执行清理逻辑。典型场景是 DirectByteBuffer 的堆外内存释放——JDK 的 Cleaner 机制就是基于虚引用实现的。原文阅读内容有帮助点赞、收藏、关注三连评论区等你

更多文章