news 2026/5/9 16:04:55

孤舟笔记 JVM篇三 JVM如何判断一个对象可以被回收?可达性分析比引用计数强在哪

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
孤舟笔记 JVM篇三 JVM如何判断一个对象可以被回收?可达性分析比引用计数强在哪

文章目录

    • 一、先说结论:两种判定方案
    • 二、引用计数法:简单但有致命缺陷
    • 三、可达性分析:JVM 的选择
    • 四、GC Roots 有哪些?
    • 五、四种引用类型与回收
      • 强引用(Strong Reference)
      • 软引用(Soft Reference)
      • 弱引用(Weak Reference)
      • 虚引用(Phantom Reference)
    • 六、finalize():最后一次逃生机会
    • 对象回收判定 全景
    • 回答技巧与点评
        • 标准回答
        • 加分回答
        • 面试官点评

个人网站

面试官问"JVM 怎么判断对象可以回收",大部分人能说出"可达性分析",但追问"为什么不用引用计数"、“GC Roots 有哪些”、“四种引用类型和回收的关系”,就答不全了。

今天咱们把 JVM 判断对象存活的机制彻底讲透。

一、先说结论:两种判定方案

方案思路JVM 选择原因
引用计数每个对象记被引用次数,0 就回收无法解决循环引用
可达性分析从 GC Roots 出发,不可达就回收能解决循环引用

一句话记住:引用计数像"数有多少人认识你",可达性分析像"从领导查组织关系"——前者数人头可能数错(互相认识),后者查链路不会漏。

二、引用计数法:简单但有致命缺陷

原理:每个对象维护一个引用计数器,被引用 +1,引用断开 -1,计数为 0 则可回收。

Objecta=newObject();// 计数 = 1Objectb=newObject();// 计数 = 1a.field=b;// b 的计数 = 2b.field=a;// a 的计数 = 2a=null;// a 的计数 = 1(b 还引用着 a)b=null;// b 的计数 = 1(a 还引用着 b)// 计数永远不为 0 → 永远不会被回收!💥 但实际上已经无法访问了

循环引用导致内存泄漏——这是引用计数法的致命缺陷。

生活类比:引用计数像"互粉"——两个人互相关注,即使谁都不看对方的内容,粉丝数也不为零,系统不会清理。

Python 用引用计数但额外加了分代 GC 来处理循环引用,JVM 直接放弃了引用计数。

三、可达性分析:JVM 的选择

原理:从 GC Roots 出发,沿引用链向下搜索,不可达的对象就是可回收的。

GC Roots ├── objA → objB → objC ← 可达,存活 ✅ ├── objD → objE ← 可达,存活 ✅ └── objX ↗ objY ↘ objX ← 不可达,回收 ❌(循环引用但无 Root 连接)

关键:循环引用不再是问题——只要没有 GC Root 能到达这个环,整个环都是垃圾。

生活类比:可达性分析像"查组织关系"——从最高领导开始查,能查到的人就是在职的,查不到的就是离职的(不管他们之间是否互相认识)。

四、GC Roots 有哪些?

GC Root 类型示例
虚拟机栈中的引用方法中的局部变量、参数
静态变量类的 static 字段
常量字符串常量池的引用
本地方法栈中的引用JNI 中的引用
同步锁synchronized 持有的对象
JVM 内部引用基本数据类型的 Class 对象、常驻异常等

最常见的是前三种:栈帧中的局部变量、静态变量、常量。

publicclassGCRootsDemo{privatestaticObjectstaticVar;// GC Root:静态变量 👈privatestaticfinalStringCONSTANT="hello";// GC Root:常量 👈publicvoidmethod(){ObjectlocalVar=newObject();// GC Root:栈帧局部变量 👈synchronized(localVar){// GC Root:锁持有的对象// ...}}// 方法返回后,localVar 不再是 GC Root}

注意:GC Root 是"时刻在变"的——方法返回后,栈帧弹出,局部变量不再是 Root。

五、四种引用类型与回收

强引用(Strong Reference)

Objectobj=newObject();// 强引用 👈

只要强引用存在,永远不会被回收。即使 OOM 也不回收强引用对象。

软引用(Soft Reference)

SoftReference<byte[]>ref=newSoftReference<>(newbyte[1024*1024]);

内存不足时才会回收。适合做缓存。

// 缓存使用示例SoftReference<Image>cache=newSoftReference<>(loadImage());Imageimg=cache.get();// 可能返回 null(已被 GC 回收)if(img==null){img=loadImage();// 重新加载cache=newSoftReference<>(img);}

弱引用(Weak Reference)

WeakReference<Object>ref=newWeakReference<>(newObject());

下一次 GC 就会回收,不管内存是否充足。适合做临时缓存。

ThreadLocalMap 的 key 就是弱引用——这就是 ThreadLocal 可能泄漏的根源。

虚引用(Phantom Reference)

PhantomReference<Object>ref=newPhantomReference<>(newObject(),queue);

不影响对象生命周期,唯一作用是在对象被回收时收到通知。必须配合 ReferenceQueue 使用。

引用类型回收时机用途
强引用永远不回收(除非引用断开)日常使用
软引用内存不足时缓存
弱引用下一次 GC临时缓存、ThreadLocal
虚引用随时可回收跟踪 GC、管理堆外内存

六、finalize():最后一次逃生机会

对象被判定不可达后,还有一次自救机会:

1. 可达性分析发现对象不可达 2. 判断是否有必要执行 finalize() ├── 没有重写 finalize() 或已调用过 → 直接回收 └── 重写了且未调用过 → 放入 F-Queue 3. Finalizer 线程执行 finalize() 4. 对象在 finalize() 中重新与 GC Root 建立引用 → 逃生成功 👈 5. 未建立引用 → 下次 GC 回收

但 finalize() 不推荐使用:执行时间不确定、可能导致对象复活、性能开销大。用 try-finally 替代。

对象回收判定 全景

对象回收判定 全景 两种方案 ├── 引用计数 ── 简单但循环引用致命 └── 可达性分析 ── JVM 选择,从 GC Roots 出发 GC Roots ├── 虚拟机栈中的引用 ├── 静态变量 ├── 常量池引用 ├── 本地方法栈引用 ├── 同步锁 └── JVM 内部引用 四种引用 ├── 强引用 ── 永不回收 ├── 软引用 ── 内存不足时回收 ├── 弱引用 ── GC 即回收 └── 虚引用 ── 仅跟踪回收通知 对象回收流程 ├── 可达性分析 → 不可达 ├── 判断 finalize() │ ├── 无需执行 → 回收 │ └── 需执行 → F-Queue ├── finalize() 自救 → 下一轮再判 └── 未自救 → 回收 口诀:引用计数有循环,可达分析是正道, GC Roots 出发点,四类引用各不同, 强不回收软看内存,弱遇 GC 虚通知, finalize 别依赖,try-finally 才靠谱。

回答技巧与点评

标准回答

JVM 使用可达性分析判断对象是否可以回收。从 GC Roots 出发,沿引用链搜索,不可达的对象即为可回收对象。GC Roots 包括虚拟机栈中的引用、静态变量、常量、本地方法栈引用、同步锁等。JVM 不使用引用计数法是因为它无法解决循环引用问题。Java 的引用分为强引用(永不回收)、软引用(内存不足时回收)、弱引用(GC 即回收)和虚引用(仅跟踪回收通知)四种,不同引用类型影响对象的回收时机。

加分回答
  1. MAT 和 jmap 分析 GC Roots:线上排查内存泄漏时,用 jmap -histo 或 MAT(Memory Analyzer Tool)dump 堆内存,可以找到"到 GC Roots 的最短路径",定位哪个引用阻止了对象被回收。这是可达性分析在工具层面的实战应用
  2. Cleaner 和虚引用:Java 9 引入了 Cleaner 类替代 finalize(),内部用虚引用 + ReferenceQueue 实现。当对象被回收时,虚引用进入队列,Cleaner 的清理线程执行清理逻辑。这是管理堆外内存(DirectByteBuffer)的标准方式——比 finalize() 更安全、更可控
  3. 三色标记法:可达性分析的具体实现是三色标记——白色(未访问)、灰色(已访问但引用未处理完)、黑色(已访问且引用处理完)。最终白色的对象就是垃圾。CMS 和 G1 的并发标记都基于三色标记,用写屏障解决"漏标"问题
面试官点评

这道题考的是你对垃圾回收理论基础的理解。能说出"可达性分析、GC Roots"是基本要求,能讲清楚为什么不用引用计数、四种引用类型的区别,才算及格。如果你能提到 MAT 工具的使用、Cleaner 替代 finalize、三色标记法的实现,面试官会认为你对 GC 的理解不只在判定层面,还延伸到了工具和算法实现。

原文阅读


内容有帮助?点赞、收藏、关注三连!评论区等你 💪

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/9 16:03:36

构建企业级MLOps平台:从数据湖到自动调度的端到端实践

1. 项目概述&#xff1a;从数据到模型的“高速公路”构想在机器学习项目从实验室走向生产的过程中&#xff0c;我们常常会陷入一种“沼泽地”式的困境。数据科学家在Jupyter Notebook里精心调优的模型&#xff0c;一旦需要处理TB级的实时数据、应对动态变化的计算资源、并保证线…

作者头像 李华
网站建设 2026/5/9 16:03:34

CANN/opbase算子缓存容器API

op_cache_container 【免费下载链接】opbase 本项目是CANN算子库的基础框架库&#xff0c;为算子提供公共依赖文件和基础调度能力。 项目地址: https://gitcode.com/cann/opbase 本章接口为预留接口&#xff0c;后续有可能变更或废弃&#xff0c;不建议开发者使用&#…

作者头像 李华
网站建设 2026/5/9 16:00:43

CANN/asc-devkit:AllocMutexID互斥锁分配接口

AllocMutexID (ISASI) 【免费下载链接】asc-devkit 本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言&#xff0c;原生支持C和C标准规范&#xff0c;主要由类库和语言扩展层构成&#xff0c;提供多层级API&#xff0c;满足多维场景算子开发诉求。 项目地址: https://g…

作者头像 李华
网站建设 2026/5/9 15:59:06

【2026OD新机考】【回溯】20260429-获取大写字母瓷砖拼出独特图案数量【Py/Java/C++/C/JS/Go六种语言OD真题】【欧弟算法】全网注释最详细分类最全的华子OD真题题解

文章目录 相关推荐阅读 题目描述与示例 题目描述 输入描述 输出描述 示例一 输入 输出 说明 示例二 输入 输出 说明 示例三 输入 输出 说明 解题思路 代码 解法一:哈希表维护字符数量 Python java C++ C Node JavaScript Go 解法二:直接遍历排序后的`L` Python Java C++ C No…

作者头像 李华
网站建设 2026/5/9 15:55:57

Hermes Agent框架接入Taotoken自定义模型提供方的配置要点

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 Hermes Agent框架接入Taotoken自定义模型提供方的配置要点 Hermes Agent 是一个流行的智能体开发框架&#xff0c;它允许开发者灵活…

作者头像 李华