Skip to content

JVM的垃圾回收机制

1. GC 的核心目标:自动化内存管理

GC 的核心目标就是自动地识别并回收那些不再被程序使用的内存,从而:

  1. 解放开发者:让我们不用像 C/C++程序员那样手动 mallocfree 内存,极大地降低了内存泄露和野指针等问题的风险。
  2. 保证系统稳定:通过自动化的内存回收,防止因内存耗尽而导致的程序崩溃。

2. 如何判断对象是“垃圾”?

JVM 判断一个对象是否可以被回收,主要采用的是可达性分析算法(Reachability Analysis)。

  1. 基本思想:这个算法将一系列被称为“GC Roots”的特殊对象作为起点,从这些节点开始向下搜索,所有能够被搜索到的对象都被认为是“存活”的,反之,任何从 GC Roots 不可达的对象,就被判定为“垃圾”。

  2. 哪些是 GC Roots?

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象:比如方法内部的局部变量。
    • 方法区中类静态属性引用的对象:比如类的静态变量。
    • 方法区中常量引用的对象:比如字符串常量池里的引用。
    • 本地方法栈中 JNI(即 Native 方法)引用的对象。
    • 被同步锁(synchronized)持有的对象。

3. 核心的垃圾回收算法

找到了垃圾之后,就需要用具体的算法来回收。主要有三种经典算法,它们各有优劣,是后续各种复杂回收器的基础。

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

    • 过程:分为“标记”和“清除”两个阶段。首先标记出所有需要回收的对象,然后统一回收所有被标记的对象。
    • 优点:实现简单。
    • 缺点:会产生大量的内存碎片。不连续的内存空间会导致后续分配大对象时,虽然总空间足够,但找不到一块连续的内存而触发又一次 GC。
  2. 标记-复制算法(Mark-Copy)

    • 过程:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
    • 优点:实现简单,不会产生内存碎片,运行高效。
    • 缺点:代价是牺牲了一半的内存空间,空间利用率低。
  3. 标记-整理算法(Mark-Compact)

    • 过程:标记过程仍然与“标记-清除”一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉端边界以外的内存。
    • 优点:不会产生内存碎片,也不需要牺牲一半的内存空间。
    • 缺点:因为涉及到对象的移动,所以效率相对较低。

4. 分代收集理论与内存布局

为了将不同算法的优势结合起来,现代 JVM 都采用了分代收集(Generational Collection)的理论。这个理论基于两个假说:

  1. 绝大多数对象都是“朝生夕死”的。
  2. 熬过越多次垃圾回收过程的对象就越难以消亡。

基于此,JVM 的堆内存被划分为两个主要区域:

  • 新生代(Young Generation):

    • 特点:存放生命周期短的对象。绝大多数对象都在这里创建,也在这里消亡。
    • 内部结构:分为一个伊甸园区(Eden Space)和两个幸存者区(Survivor Space S0, S1)。
    • 回收算法:新生代 GC(称为 Minor GC)非常频繁,由于存活对象少,所以采用标记-复制算法。新对象在 Eden 区分配,当 Eden 区满时触发 Minor GC,存活的对象会被复制到其中一个 Survivor 区(比如 S0),同时对象的“年龄”加 1。下次 GC 时,Eden 和 S0 中的存活对象会一起被复制到 S1 区。如此反复,当一个对象的年龄达到一定阈值(默认 15),它就会被“晋升”到老年代。
  • 老年代(Old Generation):

    • 特点:存放生命周期长的对象,或者一些无法在新生代分配的大对象。
    • 回收算法:老年代 GC(称为 Major GC 或 Full GC)的频率较低。由于存活对象多,不适合复制算法,所以通常采用标记-清除或标记-整理算法。

5. 主要的垃圾回收器

JVM 提供了多种垃圾回收器,它们是上述算法的具体实现,可以根据应用场景进行选择和组合。

  1. Serial/Serial Old:单线程回收器,简单高效,但会产生较长的“Stop-The-World”(STW,即暂停所有用户线程),适用于客户端模式。
  2. Parallel Scavenge/Parallel Old:多线程版本的 Serial,关注吞吐量(用户代码运行时间 / (用户代码运行时间 + GC 时间)),是 JDK 8 的默认回收器。
  3. CMS (Concurrent Mark Sweep):第一个以获取最短回收停顿时间为目标的回收器,关注低延迟。它在标记和清除阶段大部分工作可以和用户线程并发执行,但基于标记-清除,会产生内存碎片。
  4. G1 (Garbage-First):JDK 9 及以后的默认回收器,是一个里程碑式的存在。它将堆划分为多个独立的区域(Region),并跟踪每个 Region 里垃圾的价值,在有限的时间内优先回收价值最大的 Region。它在宏观上是标记-整理,局部看是标记-复制,能很好地平衡吞吐量和低延迟。
  5. ZGC / Shenandoah:最新的超低延迟回收器,追求在任何堆大小下都能将 STW 时间控制在几毫秒以内,几乎全程并发,是未来发展的方向。

总结来说,JVM 的 GC 机制是一个精密且自适应的系统。它通过可达性分析找到垃圾,然后基于分代理论,在不同区域采用最合适的回收算法(如新生代用复制,老年代用标记-整理),并通过具体的回收器(如 G1)来执行,最终目标是在吞 TP 量和延迟之间找到最佳平衡点,实现高效的自动化内存管理。