当前位置: 首页 > news >正文

强引用软引用弱引用虚引用,到底差在哪——我的学习笔记

说在前面:前四篇聊完了 JVM 的内存区域,按计划该聊 GC 了。但我学到 GC 之前发现还有一个东西必须先搞明白——引用类型。因为 GC 回收对象不是"看它有没有人用"这么简单,而是看"用什么类型的引用在用它"。强引用、软引用、弱引用、虚引用这四兄弟,强度不同,GC 对它们的态度也完全不同。这篇就是搞明白它们到底有什么区别。


这个问题是怎么冒出来的

之前学 GC 的时候,我脑子里一直有一个很朴素的想法:如果一个对象没人用了,GC 就把它收掉。

这话听着没错,但它太模糊了。什么叫"没人用"?是没变量指向它就算没人用,还是只有特定类型的变量才算?

后来我看到一段代码:

SoftReference<MyObject>softRef=newSoftReference<>(newMyObject());

我当时想:这不就是一个引用包了另一个引用吗?跟直接写MyObject obj = new MyObject()有什么区别?

后来才知道区别大了——区别不在"最终能不能找到这个对象",而在GC 看到这个引用的时候,会不会手下留情

于是我开始学这四种引用。


一张图看明白四种引用的强度

强引用 > 软引用 > 弱引用 > 虚引用 │ │ │ │ │ │ │ └─ GC 时回收,必须配合 ReferenceQueue │ │ └────────── 下一次 GC 一定回收 │ └─────────────────── 内存溢出前回收 └───────────────────────── 绝不回收

从强到弱,GC 的态度从"绝不碰"到"随缘碰"再到"必碰"再到"碰了还要通知你"。

下面一个一个说。


强引用——你最熟悉的一个

MyObjectobj=newMyObject();

这就是强引用。我们 99% 的代码写的都是这种。

强引用的规则非常暴力:只要这个引用还在(变量没出作用域、没被置 null),GC 就绝不动它指向的对象。

哪怕 JVM 已经快撑不住了,堆内存马上就要爆了,它也不会去回收强引用指向的对象。它宁愿抛OutOfMemoryError让程序挂掉,也不会动你的强引用。

我之前觉得这条规则太死板了——都快 OOM 了还不收?后来想明白了:设计者把"决定生死"的权利交给了程序员。你自己引用的对象,你自己负责释放。GC 不会自作主张替你收掉。


软引用——内存够就留着,不够就收

软引用比强引用"软"一点。

规则是:内存够用的时候,软引用对象不会被回收。但 JVM 发现内存快不够了,在抛出 OOM 之前,会先把软引用指向的对象收掉。

如果把强引用比作"死也要护着",那软引用就是"能守就守,守不住就算了"。

怎么用

SoftReference<MyObject>softRef=newSoftReference<>(newMyObject());MyObjectobj=softRef.get();// 有可能返回 null

注意get()方法——它可能返回 null。因为软引用对象可能已经被 GC 回收了。所以每次使用之前都要判空。

适合做什么

内存敏感缓存。

比如你搞了一个图片缓存,把用户最近看过的图片放在内存里。如果用户不断看新图片,缓存越来越大,但你又不希望这个缓存把堆撑爆——用软引用就合适。内存够的时候缓存正常用,内存紧张的时候 GC 自动把缓存清掉,优先保程序的正常运行。


弱引用——下一次 GC 必收

弱引用比软引用更低一级。

规则是:不管内存够不够,只要发生了 GC,弱引用指向的对象就会被回收。

这就很"无情"了——强引用是"死也不放",软引用是"看情况放",弱引用是"有机会就放"。

怎么用

WeakReference<MyObject>weakRef=newWeakReference<>(newMyObject());MyObjectobj=weakRef.get();// GC 之后返回 null

和软引用的区别

这是我当时最困惑的地方——软引用和弱引用到底差在哪?都是回收,不都是 GC 说了算吗?

区别在于触发条件不同

  • 软引用:只在内存要溢出的时候才回收。换句话说是"被动放"
  • 弱引用:下一次 GC 就回收,不管内存够不够。换句话说是"主动放"

如果你写了一个缓存,对象存活时间希望长一些(至少在内存不紧张的时候能留着),用软引用。如果你希望对象随时可以被 GC 清掉,不强占内存,用弱引用。

一个经典例子

我当时看到这段代码觉得挺有用的——用弱引用做一个简单的缓存:

importjava.lang.ref.WeakReference;importjava.util.HashMap;importjava.util.Map;publicclassCacheExample{privateMap<String,WeakReference<MyHeavyObject>>cache=newHashMap<>();publicMyHeavyObjectget(Stringkey){WeakReference<MyHeavyObject>ref=cache.get(key);if(ref!=null){returnref.get();// 可能返回 null}else{MyHeavyObjectobj=newMyHeavyObject();cache.put(key,newWeakReference<>(obj));returnobj;}}privatestaticclassMyHeavyObject{privatebyte[]largeData=newbyte[1024*1024*10];// 10MB}}

这个缓存的逻辑是:从 Map 里拿对象的时候先判断弱引用还在不在(有没有被 GC 干掉),在就直接用,不在就重新创建。

好处是:其他代码里如果把这个对象的强引用都释放了,GC 下一次扫描就能把这个大对象清掉,缓存不会成为内存泄漏的源头。

但是要注意——每次用get()之前都要判 null。因为这个对象随时可能被回收。初学者(比如我)很容易忘了这一条。


虚引用——最诡异的一个

虚引用是四个里面最诡异的。它的特点我列一下:

  • get()方法始终返回 null。你拿不到真正的对象。
  • 必须和ReferenceQueue一起用
  • 对象被 GC 回收时,虚引用会被放到关联的 ReferenceQueue 里,程序可以通过监控这个队列知道哪些对象被回收了。

我当时看到get()返回 null 的时候很懵——这有什么用?拿都拿不到,那要它干嘛?

后来理解它的用途了:它不是让你拿到对象干活用的,它是让你知道"这个对象什么时候被回收了"。

主要用来做什么

管理堆外内存。

比如 NIO 的DirectByteBuffer就用了虚引用。当 DirectByteBuffer 对象被 GC 回收时,虚引用会被放到队列里,后台线程从队列里取出这个引用,然后释放对应的堆外内存。

堆外内存不受 JVM 堆控制,GC 只管堆里的对象。所以需要一个机制来感知"堆里的那个对象被回收了",然后去清理堆外的内存。虚引用+ReferenceQueue 就是干这个的。


面试常问的一个坑

学完这四种引用之后,我遇到了一个很常见的面试题:

软引用和弱引用有什么区别?

我当时第一反应是:软引用在 OOM 前回收,弱引用下一次 GC 就回收。这个答案没错,但我后来觉得它不够——因为这两个在实际使用中有一个关键的场景区别:

  • 软引用适合做缓存:因为你想让缓存对象在内存还够的时候继续活着,只有系统要挂了才放掉
  • 弱引用适合做防止内存泄漏的辅助结构:比如WeakHashMap、ThreadLocal 中的弱引用,目的是不让你的引用成为 GC 的障碍

一个是"保命型",一个是"不碍事型"。这样想会清楚很多。


最后:四种引用对照表

这是我给自己总结的一张速查表:

引用类型GC 态度get() 能否拿到对象常见用途
强引用绝不回收日常写的所有代码
软引用OOM 前才回收可能拿不到(GC 后)内存敏感缓存
弱引用下次 GC 就收可能拿不到(GC 后)缓存、防内存泄漏
虚引用GC 时收,入队通知始终拿不到堆外内存管理

学完这篇再回头看一开始那个问题——“没人用了就回收”——确实太粗略了。准确的说法应该是:没有强引用指向它了,才有可能被回收。至于多快回收,看这个"没有强引用"是通过哪种引用路径来访问的。

http://www.jsqmd.com/news/1117175/

相关文章:

  • 猫抓浏览器插件终极指南:一站式网页媒体资源嗅探解决方案
  • 5分钟搭建你的大麦网抢票自动化系统:告别手动抢票的焦虑时代
  • 2026免费在线去水印工具推荐!视频图片无水印导出安全无广告
  • 嵌入式全栈技术
  • 如何用Xournal++免费打造你的终极数字笔记本?跨平台手写笔记软件完整指南
  • 3分钟上手:PotPlayer字幕翻译插件的终极使用指南
  • 从数据分布角度理解:为什么不同任务要用不同的损失函数?
  • MCP 2026高危漏洞应急响应:5步实操加固与长效管理机制
  • 注销公告登报办理指南:2026年流程、费用与规范模板
  • Selenium IDE:零代码入门Web自动化测试的最佳实践指南
  • 从Noodlophile恶意软件看版权钓鱼攻击链与防御策略
  • 2026年Word文档压缩完整指南:多种方式降低文件体积,超大文档瘦身实操技巧
  • Qwen3.7plus的web版测试发现Agent能力果然出众!
  • STM32F765ZI与MAX9744的高效音频系统设计
  • 北京登报遗失声明去哪里登报?原来手机上就能直接办!
  • MuleSoft企业级AI编排:实现LLM与ERP/SAP/CRM的可信集成
  • STM32低功耗矩阵键盘设计:硬件与软件协同优化
  • 2026企业级AI Agent选型指南:Top50厂商图谱与行业落地路径拆解
  • 3分钟解锁IDM完整版:永久激活的终极解决方案
  • Spring Boot整合Redis实战:从配置到性能优化
  • PotPlayer字幕翻译插件终极指南:3分钟实现外语视频无障碍观看
  • 终极纪元1800模组加载器完全指南:简单快速打造个性化游戏体验
  • 一图理清 WiFi 信道规划
  • 基于Wazuh与Zabbix构建服务器挖矿木马自动化检测与响应体系
  • FreeRTOS学习历程
  • 毕昇JDK 25安装教程:新手也能轻松上手的详细步骤
  • 思源宋体CN:免费开源中文宋体字体完整使用指南
  • 毕昇JDK 25社区与支持:获取帮助和参与讨论的渠道汇总
  • 专业的平衡机研发公司
  • 什么是GEO?GEO优化怎么做?五步搭建品牌 AI 可信内容资产