JVM 垃圾回收器(Garbage Collector)需要判断哪些对象是"垃圾",即不再被程序使用的对象,以便回收它们占用的内存。JVM 主要使用以下两种方法来判断对象是否是垃圾:
1. 引用计数算法 (Reference Counting):
-
原理:
- 为每个对象维护一个引用计数器。
- 当一个对象被引用时,引用计数器加 1。
- 当一个对象的引用被置为
null
或超出作用域时,引用计数器减 1。 - 当一个对象的引用计数器为 0 时,表示该对象不再被任何地方引用,可以被回收。
-
优点:
- 实现简单: 只需要维护一个计数器。
- 实时性高: 当对象的引用计数器变为 0 时,可以立即回收该对象。
-
缺点:
- 无法解决循环引用问题: 如果两个或多个对象相互引用,即使它们不再被其他对象引用,它们的引用计数器也不会变为 0,导致无法回收。
javapublic 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 使用可达性分析算法。
对象死亡的判定 :
即使通过可达性分析算法判断一个对象不可达,它也不会立即被回收。对象真正死亡需要经历至少两次标记过程:
-
第一次标记:
- 如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,则会被第一次标记。
- 会进行筛选, 判断此对象是否有必要执行
finalize()
方法。 - 如果对象没有覆盖
finalize()
方法,或者finalize()
方法已经被虚拟机调用过,则会被判定为"没有必要执行"。 - 如果对象覆盖了
finalize()
方法, 且还没有被调用过, 则会将该对象放置在一个名为 F-Queue 的队列中。
-
finalize()
方法的执行 (如果有):- 稍后由一条由虚拟机自动建立的、低调度优先级的 Finalizer 线程去执行它们的
finalize()
方法。 - 注意:
finalize()
方法是对象逃脱死亡的最后一次机会。- 在
finalize()
方法中,只要将对象重新与引用链上的任何一个对象建立关联(例如,将this
赋值给某个类变量或对象的成员变量),就可以避免被回收。 finalize()
方法只会被系统自动调用一次。如果对象在finalize()
方法中逃脱了死亡,下次再被标记时,finalize()
方法不会再被执行。- 不建议使用
finalize()
方法进行资源释放,因为它的执行时间不确定,可能会导致资源长时间得不到释放。 finalize
方法可能会导致性能问题,应尽量避免使用。
- 稍后由一条由虚拟机自动建立的、低调度优先级的 Finalizer 线程去执行它们的
-
第二次标记:
- 如果在
finalize()
方法中对象成功逃脱了死亡,则不会被回收。 - 如果在
finalize()
方法中对象没有逃脱死亡,或者对象没有覆盖finalize()
方法,或者finalize()
方法已经被虚拟机调用过,则会被第二次标记。 - 被第二次标记的对象会被放置在一个名为 F-Queue 的队列中, 等待被回收.
- 如果在
总结:
JVM 使用可达性分析算法 来判断对象是否是垃圾。从 GC Roots 开始,沿着对象引用链进行遍历,如果一个对象到 GC Roots 之间没有任何引用链相连,则说明该对象不可达,可以被回收。即使对象不可达,也不会立即被回收,需要经历至少两次标记过程,并且有机会在 finalize()
方法中逃脱死亡。