Java 中的可达性分析(Reachability Analysis)是一种确定对象是否可以被垃圾回收器回收的算法。该算法基于从一些称为“GC Roots”(垃圾收集根)的对象开始,遍历整个对象图,从而决定哪些对象是可达的(即仍在使用中)以及哪些对象是不可达的(即不再被使用,可以被回收)。

GC Roots 的定义

GC Roots 是垃圾回收过程中的起点对象,它们是一组可以直接或间接被 JVM 访问的对象集合,没有 GC Roots 引用的对象则被认为是不可达的。GC Roots 包括但不限于以下几类:

  1. 虚拟机栈中引用的对象:局部变量表中的对象引用。

  2. 本地方法栈中引用的对象:JNI(Java Native Interface)中引用的本地对象。

  3. 方法区中常量引用的对象:例如字符串字面量和类静态变量所引用的对象。

  4. 方法区中静态属性引用的对象:类的静态字段所引用的对象。

  5. 运行时常量池引用的对象:例如字符串字面量的引用。

  6. 其他系统级的引用:如 Java 虚拟机内部的某些特殊对象,如线程对象、类加载器对象等。

可达性分析过程
  1. 识别 GC Roots:首先,JVM 会确定所有活跃的 GC Roots 对象。

  2. 遍历对象图:从 GC Roots 开始,沿着它们的引用关系向下遍历,能够被遍历到的对象被视为可达的,即还在使用的对象。

  3. 标记不可达对象:未被遍历到的对象被认为是不可达的,意味着它们不再被任何可达对象引用。

  4. 准备回收:虽然对象被标记为不可达,但还不一定会立即被回收。在 HotSpot 虚拟机中,不可达的对象需要经过至少两次标记过程确认确实无法逃脱回收的命运,特别是在存在finalize()方法可能复活对象的情况下。

OopMap 优化

在 HotSpot 虚拟机中,为了加速可达性分析过程,特别是根节点枚举阶段,使用了一种称为 OopMap(Ordinary Object Pointer Map)的数据结构。OopMap 记录了堆中对象内存区域中哪些位置存放着对象的引用,这样垃圾收集器在扫描时可以直接定位到引用的位置,而无需遍历整个方法区或栈空间,大大提高了效率。

finalize() 方法与自救

即使对象在可达性分析中被标记为不可达,它仍然有机会通过覆盖Object类的finalize()方法来自救。在对象被回收前,JVM 会调用这个方法,如果在这个方法中对象重新与 GC Roots 建立了关联,则对象可以避免被回收。但这种自救机制不建议使用,因为它会影响垃圾回收的性能,并且finalize()方法已被标记为不鼓励使用并在未来版本中可能会被移除。

垃圾回收算法

Java垃圾回收(Garbage Collection, GC)算法是Java虚拟机(JVM)用于自动管理内存的重要组件,其目标是自动回收不再使用的对象所占用的内存空间,以防止内存泄漏和提高内存利用率。以下是几种常见的Java垃圾回收算法:

  1. 标记-清除(Mark-Sweep)算法

  • 这是最基础的垃圾回收算法,分为两个阶段:首先标记出所有需要回收的对象,然后统一回收这些被标记的对象。该算法简单,但有两个主要缺点:一是效率不高,标记和清除两个过程都需要遍历内存;二是会产生内存碎片,影响大对象的分配。

复制(Copy)算法

  • 该算法将可用内存分为两块,每次只使用其中一块。当一块内存用完,就将还存活的对象复制到另一块上面,然后一次性清理掉已使用过的那块内存。这样做的好处是可以消除内存碎片,但缺点是内存的有效使用率降低到了原来的一半。

标记-整理(Mark-Compact)算法

  • 结合了标记-清除算法和复制算法的优点。同样首先标记出所有需要回收的对象,然后将所有存活的对象压缩到内存的一端,再清理掉边界外的内存。这样既解决了内存碎片问题,又不需要牺牲一半的内存空间。

分代收集算法

  • Java堆内存通常被划分为新生代和老年代。新生代对象存活时间短,频繁回收,适合使用复制算法;老年代对象存活时间长,回收频率低,适合使用标记-清除或标记-整理算法。分代收集是上述算法的组合使用,是现代JVM中最常用的垃圾回收策略。

在实际应用中,JVM会根据不同的场景选择合适的算法或它们的变种。例如,HotSpot JVM就实现了多种垃圾收集器,包括但不限于Serial、Parallel、CMS(Concurrent Mark-Sweep)和G1(Garbage First),每种收集器可能采用不同的垃圾回收算法组合,以达到最佳的性能表现。

此外,Java 9及之后版本引入了ZGC和Shenandoah等新的垃圾收集器,进一步优化了垃圾回收的效率和暂停时间。