java面试必问24:Java 垃圾回收机制:从对象判死到分代回收,一篇讲透
Java 垃圾回收机制从对象判死到分代回收一篇讲透面试官“Java 如何判断一个对象可以被回收”你“两种方式引用计数法和可达性分析。主流 JVM 使用可达性分析从 GC Roots 出发不可达的对象就是垃圾。”面试官“那 GC Roots 包括哪些引用计数法有什么缺陷”你“……”很多人能背出“可达性分析”但一问到具体算法、分代回收原理、GC Roots 细节就卡壳了。本文从对象死亡判定到垃圾回收算法结合 HotSpot 实现彻底讲透 Java 垃圾回收机制。一、垃圾回收GC是什么垃圾回收是指自动管理内存的一种机制自动识别并回收不再使用的对象释放内存空间。Java 程序员不需要手动free或deleteJVM 会帮我们完成这项工作。垃圾回收的核心问题有两个哪些内存需要回收—— 对象死亡判定如何回收—— 垃圾回收算法二、判断对象死亡的两种方式1. 引用计数法Reference Counting原理每个对象维护一个引用计数器每当有一个地方引用它计数器 1引用失效时计数器 -1。计数器为 0 的对象就是垃圾。优点简单高效实时性好可以立即回收垃圾。缺点致命缺陷循环引用对象 A 引用 BB 引用 A除此之外无其他引用。两者计数器都不为 0永远不会被回收导致内存泄漏。需要额外空间存储计数器且加减操作频繁。应用Python、PHP 等语言早期使用但都搭配了其他算法解决循环引用。JVM 没有采用引用计数法。2. 可达性分析Reachability Analysis原理从一组称为GC Roots的根对象出发通过引用链向下搜索形成引用网络。在这个网络中的对象是“可达”的存活不在网络中的对象就是“不可达”的可回收。优点天然解决循环引用问题——A 和 B 互相引用但如果它们没有任何 GC Roots 引用就会被判定为不可达一起回收。GC Roots 包括虚拟机栈栈帧中的局部变量表中引用的对象方法区中静态属性引用的对象如static字段方法区中常量引用的对象如final常量本地方法栈中 JNI 引用的对象Native 方法Java 虚拟机内部的引用基本类型的 Class 对象、常驻异常对象等所有被同步锁synchronized持有的对象反映 Java 虚拟机内部情况的 JMXBean、JVMTI 回调等注意即使对象被可达性分析判定为不可达也不一定会立即回收。它至少需要经过两次标记过程第一次标记并筛选是否执行finalize()第二次标记真正回收。finalize()方法已被 JDK 9 标记为废弃不推荐使用。结论HotSpot JVM 采用可达性分析作为对象存活判定算法。三、常见垃圾回收算法有了“哪些是垃圾”接下来就是“如何回收垃圾”。垃圾回收算法是垃圾收集器实现的理论基础。1. 标记-清除Mark-Sweep过程标记遍历所有 GC Roots标记出所有存活对象。清除线性遍历堆内存回收所有未被标记的对象的内存。图示初始: [A][B][C][D][E] A、C、E 存活 标记后: [✓][×][✓][×][✓] ✓存活×垃圾 清除后: [A][ ][C][ ][E] 内存碎片优点简单不需要移动对象。缺点效率问题标记和清除都需要遍历对象越多越慢。空间碎片清除后产生大量不连续内存碎片导致大对象无法分配提前触发另一次 GC。应用场景CMS 收集器的“并发清除”阶段使用了标记-清除算法的变种。2. 复制算法Copying过程将内存分为大小相等的两块From 和 To每次只使用其中一块。当这一块用满时将存活对象复制到另一块然后一次性清空当前块。图示初始: From [A][B][C][D] To [空] 存活 A、C → 复制到 To From 清空: From [空] To [A][C] 交换角色: From [A][C] To [空]优点实现简单运行高效只需移动指针无需处理碎片。无内存碎片分配新对象时只需指针碰撞bump-the-pointer。缺点内存利用率低只能用一半内存。对象存活率高时复制开销大。应用新生代 GC如 Serial、ParNew、Parallel Scavenge因为新生代对象存活率低适合复制算法。HotSpot 将新生代分为 Eden Survivor两块通常是 8:1:1而不是 1:1提高了内存利用率。3. 标记-整理Mark-Compact过程标记与标记-清除相同标记存活对象。整理将所有存活对象向一端移动然后直接清理边界以外的内存。图示标记后: [A][×][C][×][E] A、C、E 存活 整理后: [A][C][E][ ][ ] 存活对象靠左右边空闲连续优点无内存碎片适合老年代对象存活率高。缺点移动对象需要更新所有引用地址成本比标记-清除高。应用老年代 GC如 Serial Old、Parallel Old。4. 分代回收Generational Collection核心思想根据对象存活周期的不同将堆内存划分为不同区域采用最适合的回收算法。HotSpot 堆划分新生代Young Generation对象存活率低使用复制算法。分为 Eden 区和两块 Survivor 区From 和 To默认比例 8:1:1。老年代Old Generation对象存活率高使用标记-清除或标记-整理算法取决于收集器。为什么分代有效大部分对象朝生夕灭如局部变量、临时对象。老年代对象生命周期长回收频率低。分代可以针对不同区域采用不同策略提升整体效率。对象晋升Promotion新生代经过多次 GC 仍存活的对象会晋升到老年代。大对象超过阈值直接分配到老年代。卡表Card Table用于记录老年代对新生代的引用避免每次 Young GC 都扫描整个老年代。四、垃圾回收算法对比算法内存利用率碎片问题移动对象适用场景标记-清除100%有否老年代CMS复制50%或更高如 90% 通过 Survivor无是新生代标记-整理100%无是老年代分代回收高组合使用基本无部分综合五、常见面试追问Q1引用计数法为什么不用于 JVM循环引用无法解决例如 A 引用 BB 引用 A且外部没有引用计数器永远不为 0内存泄漏。Q2可达性分析中的 GC Roots 包括哪些见上文列表。尤其注意被synchronized持有的对象、JNI 全局引用、活跃线程等也是 GC Roots。Q3标记-清除和标记-整理的区别标记-清除不移动对象产生碎片标记-整理移动对象消除碎片但成本更高。Q4为什么新生代使用复制算法新生代对象存活率低每次回收只有少量对象存活复制开销小且无碎片。通过 Survivor 分区8:1:1内存浪费仅 10%可接受。Q5直接调用System.gc()会发生什么会建议 JVM 执行 Full GC但 JVM 不保证立即执行。频繁调用会严重影响性能应避免。Q6finalize()方法的作用和问题对象被回收前如果覆盖了finalize()会被调用仅一次。但该方法执行时间不确定且可能“复活”对象已被 JDK 9 标记为废弃。推荐使用try-with-resources或Cleaner。六、总结判定方式核心优缺点引用计数法计数器简单但无法解决循环引用可达性分析GC Roots 引用链无循环引用问题主流方案回收算法核心适用区域标记-清除标记 清除老年代CMS复制分半复制新生代标记-整理标记 移动老年代Serial Old、Parallel Old分代回收组合策略整个堆一句话记住垃圾回收可达分析判生死分代回收用对法新生复制老整理标记清除防碎片。垃圾回收算法是理解 JVM 调优和选择垃圾收集器的基础。掌握这些原理才能看懂 GC 日志、优化停顿时间、排查内存问题。希望这篇文章能帮你彻底掌握 Java 垃圾回收机制欢迎继续讨论。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2557520.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!