Skip to content

JVM的垃圾回收机制

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

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

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

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

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

  2. 哪些是GC Roots?

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

核心的垃圾回收算法

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

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

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

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

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

分代收集理论与内存布局

为了将不同算法的优势结合起来,现代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)的频率较低。由于存活对象多,不适合复制算法,所以通常采用标记-清除或标记-整理算法。

主要的垃圾回收器

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量和延迟之间找到最佳平衡点,实现高效的自动化内存管理。