Java虚拟机垃圾回收机制详解

Java虚拟机垃圾回收机制详解

Java 的自动内存管理机制是其一大特色,它让开发者无需手动分配和释放内存,从而避免了内存泄漏和悬空指针等常见问题。这项机制的核心便是垃圾回收(Garbage Collection,GC)。本文将深入探讨 JVM 的垃圾回收机制,涵盖垃圾回收算法、垃圾收集器以及性能调优等方面。

一、 对象的生命周期

在理解垃圾回收之前,我们需要先了解 Java 对象的生命周期。一个 Java 对象从创建到销毁,大致经历以下几个阶段:

  1. 创建阶段: 使用 new 关键字创建对象,在堆内存中分配空间,并初始化对象。
  2. 应用阶段: 对象被程序使用,其成员变量被访问和修改。
  3. 不可达阶段: 对象不再被任何活动线程引用,即变成垃圾,等待被回收。
  4. 回收阶段: 垃圾回收器回收不可达对象的内存空间,使其可以重新被分配。

二、 如何判断对象是否存活?

判断对象是否存活,主要有两种方法:

  • 引用计数法: 为每个对象维护一个引用计数器,每当有一个地方引用它时,计数器值加 1;当引用失效时,计数器值减 1。当计数器值为 0 时,该对象被认为是垃圾。这种方法简单,但无法解决循环引用的问题。例如,对象 A 引用对象 B,对象 B 又引用对象 A,即使它们都不可达,引用计数也不为 0。

  • 可达性分析算法: 从一系列称为 "GC Roots" 的根对象开始向下搜索,所走过的路径称为引用链。如果一个对象到 GC Roots 没有任何引用链相连,则证明此对象是不可达的。GC Roots 包括:

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

三、 垃圾回收算法

JVM 使用多种垃圾回收算法来回收不可达对象,常见的算法包括:

  • 标记-清除算法 (Mark-Sweep): 首先标记出所有需要回收的对象,然后统一回收所有被标记的对象。缺点是效率不高,且会产生大量不连续的内存碎片。

  • 复制算法 (Copying): 将内存分为两块,每次只使用其中一块。当这一块内存用完了,就将存活的对象复制到另一块上,然后把已使用的内存空间一次清理掉。优点是效率高,不会产生内存碎片。缺点是内存利用率低。适用于新生代,因为新生代对象存活率低。

  • 标记-整理算法 (Mark-Compact): 标记过程与“标记-清除”算法相同,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。 适用于老年代,因为老年代对象存活率高。

  • 分代收集算法 (Generational Collection): 根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代又分为 Eden 区、From Survivor 区、To Survivor 区。根据各代的特点采用最适当的收集算法。新生代采用复制算法,老年代采用标记-清除或标记-整理算法。

四、 垃圾收集器

垃圾收集器是垃圾回收算法的具体实现。不同的垃圾收集器有不同的特点,适用于不同的场景。以下是几种常见的垃圾收集器:

  • Serial 收集器: 单线程收集器,在进行垃圾回收时会暂停所有用户线程(Stop The World)。适用于单 CPU 环境。

  • ParNew 收集器: Serial 收集器的多线程版本,适用于多 CPU 环境。

  • Parallel Scavenge 收集器: 关注吞吐量(Throughput)的收集器,目标是尽可能缩短垃圾收集时间。

  • Serial Old 收集器: Serial 收集器的老年代版本。

  • Parallel Old 收集器: Parallel Scavenge 收集器的老年代版本。

  • CMS (Concurrent Mark Sweep) 收集器: 以获取最短回收停顿时间为目标的收集器,基于“标记-清除”算法实现,并发收集、低停顿。

  • G1 (Garbage-First) 收集器: 将堆内存划分成多个大小相等的独立区域(Region),并跟踪各个 Region 里面的垃圾堆积价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。

  • ZGC (Z Garbage Collector): 低延迟垃圾收集器,目标是在尽可能短的时间内完成垃圾回收,最大停顿时间不超过 10ms。

五、 内存分配与回收策略

对象通常在新生代的 Eden 区分配内存。当 Eden 区没有足够空间时,虚拟机将发起一次 Minor GC。Minor GC 主要回收新生代的垃圾。存活下来的对象会被复制到 Survivor 区。当 Survivor 区也满时,会将对象复制到老年代。大对象直接进入老年代。当老年代空间不足时,虚拟机将发起一次 Full GC,Full GC 会回收整个堆内存,包括新生代和老年代。

六、 性能调优

垃圾回收的性能调优是一个复杂的过程,需要根据具体的应用场景进行调整。以下是一些常用的调优参数:

  • -Xms: 设置初始堆大小。
  • -Xmx: 设置最大堆大小。
  • -Xmn: 设置新生代大小。
  • -XX:SurvivorRatio: 设置新生代中 Eden 区与 Survivor 区的比例。
  • -XX:+UseSerialGC: 使用 Serial 收集器。
  • -XX:+UseParNewGC: 使用 ParNew 收集器。
  • -XX:+UseParallelGC: 使用 Parallel Scavenge 收集器。
  • -XX:+UseParallelOldGC: 使用 Parallel Old 收集器。
  • -XX:+UseConcMarkSweepGC: 使用 CMS 收集器。
  • -XX:+UseG1GC: 使用 G1 收集器。
  • -XX:+UseZGC: 使用 ZGC 收集器。

七、展望未来:持续演进的垃圾回收

Java 的垃圾回收机制在不断发展,新的垃圾收集器和算法不断涌现,例如 ZGC 和 Shenandoah 等,它们的目标都是尽可能降低垃圾回收的停顿时间,提高应用的性能和响应速度。未来,随着硬件技术的发展和软件技术的进步,Java 的垃圾回收机制将会更加高效和智能。

通过深入理解 JVM 的垃圾回收机制,开发者可以更好地编写高性能、高可靠性的 Java 应用程序,并根据实际需求进行性能调优,从而最大程度地发挥 Java 的优势。

THE END