JVM 垃圾回收机制:可达性分析算法详解
虽然方法区也有 GC,但条件更为苛刻(如所有实例被回收、ClassLoader 被回收等),本文主要讲解堆内存。
可达性分析算法
垃圾收集器通常采用可达性分析算法。其核心规则是:如果某个对象没有通过引用链连接到 GC Roots,则表明该对象不可达,会被判定为可回收对象。
GC Roots 是一个数据结构,包含多种引用类型。例如,在方法中 new 出来的对象会被强引用连接。
根据 JVM 内存区域的分配,new Student() 是在 Java 堆中开辟了一块空间用于存放对象数据。Java 中虽取消了显式指针,但变量本质上起到了指针的作用。如果将变量设置为 null,代表将引用指向的位置断开,此时 Java 堆中的对象就会被可达性算法检测为'GC Roots 到该对象不可达'。
finalize 方法
finalize 是所有类中都存在的一个方法,它只有在 GC 过程中会被触发使用,而且只会被使用一次,并且已被官方弃用。
书中提到:它并不能等同于 C 和 C++ 语言中的析构函数,而是 Java 刚诞生时为了使传统 C/C++ 程序员更容易接受 Java 所做出的一项妥协。它的运行代价高昂、不确定性大、无法保证各个对象的调用顺序,如今已被官方明确声明为不推荐使用的语法。
在 Java 源码中也如是说到:子类重写 finalize 方法以处置系统资源或执行其他清理。finalize 的一般约定是:如果 Java 虚拟机确定任何尚未死亡的线程都无法再访问此对象(没被 GC Roots 引用连接),则调用 finalize。
除非是由于其他已准备就绪的对象或类的 finalize 所采取的行动。finalize 方法可以采取任何行动,包括使此对象再次可用于其他线程;这句话的核心是解释 finalize 方法的一个关键特性:对象可以在自己的 finalize 方法中'复活'自己,或者复活其他对象。
然而,finalize 的通常目的是在对象被不可撤销地丢弃之前执行清理操作。例如,表示输入/输出连接的对象的 finalize 方法可能会在对象被永久丢弃之前执行显式 I/O 事务以断开连接。
Java 虚拟机对任何给定对象调用 finalize 方法的次数都不会超过一次。
使用 finalize 可能会导致安全性、性能和可靠性方面的问题。其中虚拟机并不承诺 finalize 的内容会被执行完成,如果一个对象的 finalize 方法执行非常缓慢,或者发生了死循环(例如 while(true)),将导致 F-Queue 队列一直堆积,最终可能导致整个内存回收子系统崩溃,甚至 OOM(内存溢出)。
两次标记
当满足以下两种需求时,才会触发两次标记(如果 finalize 没被重写,那么在第一次 GC 标记后就会直接给对象空间清除了):
- Java 堆中的对象不被 GC Roots 引用连接
finalize被重写
第一次标记
- Step 1(可达性分析): 发现对象不可达。
- Step 2(第一次标记/筛选): 判断该对象是否有必要执行
finalize()方法。- 如果对象没有覆盖
finalize()方法,或者finalize()已经被虚拟机调用过,那么虚拟机视为'没有必要执行'。—— 这种情况下,对象直接被判定为'死亡',等待回收,不会进入 F-Queue。 - 只有'有必要执行'的对象,才会被放入 F-Queue。
- 如果对象没有覆盖
接下来会由虚拟机自动建立的、低调度优先级的 Finalizer 线程去调用队列中对象的 finalize 方法,对象如果在 finalize 中将自己赋值给一个变量(即赋值给 GC Roots 中的一个对象)。


