JVM 垃圾回收器是如何判断一个对象是否要回收?

JVM 垃圾回收器(Garbage Collector)需要判断哪些对象是"垃圾",即不再被程序使用的对象,以便回收它们占用的内存。JVM 主要使用以下两种方法来判断对象是否是垃圾:

1. 引用计数算法 (Reference Counting):

  • 原理:

    • 为每个对象维护一个引用计数器。
    • 当一个对象被引用时,引用计数器加 1。
    • 当一个对象的引用被置为 null 或超出作用域时,引用计数器减 1。
    • 当一个对象的引用计数器为 0 时,表示该对象不再被任何地方引用,可以被回收。
  • 优点:

    • 实现简单: 只需要维护一个计数器。
    • 实时性高: 当对象的引用计数器变为 0 时,可以立即回收该对象。
  • 缺点:

    • 无法解决循环引用问题: 如果两个或多个对象相互引用,即使它们不再被其他对象引用,它们的引用计数器也不会变为 0,导致无法回收。
    java 复制代码
    public class ReferenceCountingGC {
        public Object instance = null;
    
        public static void main(String[] args) {
            ReferenceCountingGC objA = new ReferenceCountingGC();
            ReferenceCountingGC objB = new ReferenceCountingGC();
    
            objA.instance = objB; // objA 引用 objB
            objB.instance = objA; // objB 引用 objA
    
            objA = null; // objA 不再引用对象
            objB = null; // objB 不再引用对象
    
            // 此时,objA 和 objB 相互引用,引用计数器都不为 0,无法被回收 (即使它们已不再被使用)
            // ...
        }
    }
  • HotSpot VM 是否使用: HotSpot VM 不使用引用计数算法,因为它无法解决循环引用问题。

2. 可达性分析算法 (Reachability Analysis) (根搜索算法):

  • 原理:

    • 从一组称为 "GC Roots" 的根对象开始,沿着对象引用链进行遍历。
    • 如果一个对象到 GC Roots 之间没有任何引用链相连,则说明该对象不可达,可以被回收。
    • 可达的对象会被标记, 不可达的对象被认为是垃圾.
  • GC Roots:

    • 虚拟机栈 (VM Stack) 中引用的对象: 局部变量、方法参数等引用的对象。
    • 方法区中类静态属性引用的对象: static 变量引用的对象。
    • 方法区中常量引用的对象: final 修饰的常量引用的对象。
    • 本地方法栈中 JNI (Java Native Interface) 引用的对象: Native 方法引用的对象。
    • Java 虚拟机内部的引用: 例如, 基本数据类型对应的 Class 对象, 一些常驻的异常对象(NullPointerException, OutOfMemoryError 等), 系统类加载器.
    • 被同步锁 (synchronized 关键字) 持有的对象。
    • 反应Java虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等。
  • 优点:

    • 可以解决循环引用问题。
    • 准确性高。
  • 缺点:

    • 需要暂停应用程序 (Stop-The-World): 在进行可达性分析时,需要暂停所有 Java 线程,以保证分析结果的准确性。
    • 实现复杂。
  • HotSpot VM 是否使用: HotSpot VM 使用可达性分析算法

对象死亡的判定 :

即使通过可达性分析算法判断一个对象不可达,它也不会立即被回收。对象真正死亡需要经历至少两次标记过程:

  1. 第一次标记:

    • 如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,则会被第一次标记。
    • 会进行筛选, 判断此对象是否有必要执行 finalize() 方法。
    • 如果对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,则会被判定为"没有必要执行"。
    • 如果对象覆盖了 finalize() 方法, 且还没有被调用过, 则会将该对象放置在一个名为 F-Queue 的队列中。
  2. finalize() 方法的执行 (如果有):

    • 稍后由一条由虚拟机自动建立的、低调度优先级的 Finalizer 线程去执行它们的 finalize() 方法。
    • 注意:
      • finalize() 方法是对象逃脱死亡的最后一次机会。
      • finalize() 方法中,只要将对象重新与引用链上的任何一个对象建立关联(例如,将 this 赋值给某个类变量或对象的成员变量),就可以避免被回收。
      • finalize() 方法只会被系统自动调用一次。如果对象在 finalize() 方法中逃脱了死亡,下次再被标记时,finalize() 方法不会再被执行。
      • 不建议使用 finalize() 方法进行资源释放,因为它的执行时间不确定,可能会导致资源长时间得不到释放。
      • finalize 方法可能会导致性能问题,应尽量避免使用。
  3. 第二次标记:

    • 如果在 finalize() 方法中对象成功逃脱了死亡,则不会被回收。
    • 如果在 finalize() 方法中对象没有逃脱死亡,或者对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,则会被第二次标记。
    • 被第二次标记的对象会被放置在一个名为 F-Queue 的队列中, 等待被回收.

总结:

JVM 使用可达性分析算法 来判断对象是否是垃圾。从 GC Roots 开始,沿着对象引用链进行遍历,如果一个对象到 GC Roots 之间没有任何引用链相连,则说明该对象不可达,可以被回收。即使对象不可达,也不会立即被回收,需要经历至少两次标记过程,并且有机会在 finalize() 方法中逃脱死亡。

相关推荐
Code成立8 小时前
《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》第2章 Java内存区域与内存溢出异常
java·jvm·jvm内存模型·jvm内存区域
南汐以墨11 小时前
探秘JVM内部
java·jvm
云闲不收18 小时前
垃圾回收——三色标记法(golang使用)
jvm·算法·golang
云之兕1 天前
Java内存模型详解:堆、栈、方法区
java·开发语言·jvm
少JSQ1 天前
深入浅出Java虚拟机(JVM)-JVM内存区域
jvm
陳長生.1 天前
JAVA EE_多线程-初阶(二)
java·开发语言·jvm·java-ee
快来卷java1 天前
JVM虚拟机篇(五):深入理解Java类加载器与类加载机制
java·jvm·mysql
程序猿chen1 天前
《JVM考古现场(十六):太初奇点——从普朗克常量到宇宙弦的编译风暴》
jvm·git·后端·程序人生·金融·java-ee·量子计算
Excuse_lighttime2 天前
JAVA阻塞队列
java·开发语言·jvm