孤舟笔记 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 则可回收。ObjectanewObject();// 计数 1ObjectbnewObject();// 计数 1a.fieldb;// b 的计数 2b.fielda;// a 的计数 2anull;// a 的计数 1b 还引用着 abnull;// b 的计数 1a 还引用着 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静态变量 privatestaticfinalStringCONSTANThello;// GC Root常量 publicvoidmethod(){ObjectlocalVarnewObject();// GC Root栈帧局部变量 synchronized(localVar){// GC Root锁持有的对象// ...}}// 方法返回后localVar 不再是 GC Root}注意GC Root 是时刻在变的——方法返回后栈帧弹出局部变量不再是 Root。五、四种引用类型与回收强引用Strong ReferenceObjectobjnewObject();// 强引用 只要强引用存在永远不会被回收。即使 OOM 也不回收强引用对象。软引用Soft ReferenceSoftReferencebyte[]refnewSoftReference(newbyte[1024*1024]);内存不足时才会回收。适合做缓存。// 缓存使用示例SoftReferenceImagecachenewSoftReference(loadImage());Imageimgcache.get();// 可能返回 null已被 GC 回收if(imgnull){imgloadImage();// 重新加载cachenewSoftReference(img);}弱引用Weak ReferenceWeakReferenceObjectrefnewWeakReference(newObject());下一次 GC 就会回收不管内存是否充足。适合做临时缓存。ThreadLocalMap 的 key 就是弱引用——这就是 ThreadLocal 可能泄漏的根源。虚引用Phantom ReferencePhantomReferenceObjectrefnewPhantomReference(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 或 MATMemory Analyzer Tooldump 堆内存可以找到到 GC Roots 的最短路径定位哪个引用阻止了对象被回收。这是可达性分析在工具层面的实战应用Cleaner 和虚引用Java 9 引入了 Cleaner 类替代 finalize()内部用虚引用 ReferenceQueue 实现。当对象被回收时虚引用进入队列Cleaner 的清理线程执行清理逻辑。这是管理堆外内存DirectByteBuffer的标准方式——比 finalize() 更安全、更可控三色标记法可达性分析的具体实现是三色标记——白色未访问、灰色已访问但引用未处理完、黑色已访问且引用处理完。最终白色的对象就是垃圾。CMS 和 G1 的并发标记都基于三色标记用写屏障解决漏标问题面试官点评这道题考的是你对垃圾回收理论基础的理解。能说出可达性分析、GC Roots是基本要求能讲清楚为什么不用引用计数、四种引用类型的区别才算及格。如果你能提到 MAT 工具的使用、Cleaner 替代 finalize、三色标记法的实现面试官会认为你对 GC 的理解不只在判定层面还延伸到了工具和算法实现。原文阅读内容有帮助点赞、收藏、关注三连评论区等你