孤舟笔记 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 即回收)和虚引用(仅跟踪回收通知)四种,不同引用类型影响对象的回收时机。
加分回答
- MAT 和 jmap 分析 GC Roots:线上排查内存泄漏时,用 jmap -histo 或 MAT(Memory Analyzer Tool)dump 堆内存,可以找到"到 GC Roots 的最短路径",定位哪个引用阻止了对象被回收。这是可达性分析在工具层面的实战应用
- Cleaner 和虚引用:Java 9 引入了 Cleaner 类替代 finalize(),内部用虚引用 + ReferenceQueue 实现。当对象被回收时,虚引用进入队列,Cleaner 的清理线程执行清理逻辑。这是管理堆外内存(DirectByteBuffer)的标准方式——比 finalize() 更安全、更可控
- 三色标记法:可达性分析的具体实现是三色标记——白色(未访问)、灰色(已访问但引用未处理完)、黑色(已访问且引用处理完)。最终白色的对象就是垃圾。CMS 和 G1 的并发标记都基于三色标记,用写屏障解决"漏标"问题
面试官点评
这道题考的是你对垃圾回收理论基础的理解。能说出"可达性分析、GC Roots"是基本要求,能讲清楚为什么不用引用计数、四种引用类型的区别,才算及格。如果你能提到 MAT 工具的使用、Cleaner 替代 finalize、三色标记法的实现,面试官会认为你对 GC 的理解不只在判定层面,还延伸到了工具和算法实现。
原文阅读
内容有帮助?点赞、收藏、关注三连!评论区等你 💪
