深入解析JVM主流垃圾收集器:从Serial到G1的演进与实战配置
如果说垃圾回收算法(如标记-清除、复制、标记-整理)是内存管理的理论基石,那么垃圾收集器(Garbage Collector, GC)则是这些理论在 JVM 中的具体工程实现。不同的收集器针对不同的硬件环境、堆内存大小以及应用场景(如高吞吐量或低延迟)进行了深度优化。
Serial 与 Serial Old:单线程的极致
Serial 收集器是最基础、历史最悠久的收集器。它的核心特征是"单线程",这不仅意味着它仅使用一个 CPU 核心或一条线程来执行内存回收,更关键的是,它在执行 GC 时必须挂起所有用户线程(即 Stop-The-World, STW),直到回收完成。
尽管 STW 听起来是个缺点,但在单核处理器或客户端模式(Client Mode)下,Serial 收集器由于没有多线程上下文切换的开销,反而能达到最高的内存回收效率。Serial Old 则是其在老年代的延伸,采用标记-整理算法。
# 启用 Serial 收集器(新生代与老年代均使用单线程模式)
JAVA_OPTS="-XX:+UseSerialGC"
ParNew:多核环境下的新生代搭档
ParNew 本质上是 Serial 收集器的多线程版本。除了利用多线程并行执行垃圾回收外,其核心算法和 STW 机制与 Serial 完全一致。在单核环境下,ParNew 由于线程调度开销,性能不如 Serial;但在多核服务器环境中,其并行处理能力使其成为新生代回收的首选,且通常是 CMS 收集器的默认新生代搭档。
# 强制指定使用 ParNew 作为新生代收集器
JAVA_OPTS="-XX:+UseParNewGC"
Parallel Scavenge 与 Parallel Old:吞吐量优先
与关注停顿时间的收集器不同,Parallel Scavenge(新生代)和 Parallel Old(老年代)的设计目标是最大化系统的吞吐量。吞吐量定义为 CPU 用于执行用户代码的时间占总运行时间的比例。
高吞吐量意味着 CPU 时间被高效用于业务计算,非常适合后台批处理或科学计算任务。Parallel Scavenge 引入了自适应调节策略(Ergonomics),允许 JVM 根据运行时监控数据自动调整堆大小和代际比例。
# 启用 Parallel 收集器组合,并开启自适应大小策略
JAVA_OPTS="-XX:+UseParallelGC -XX:+UseParallelOldGC -XX:+UseAdaptiveSizePolicy"
# 若需手动干预吞吐量指标,可配置以下参数:
# JAVA_OPTS="$JAVA_OPTS -XX:MaxGCPauseMillis=100" # 目标最大停顿时间
# JAVA_OPTS="$JAVA_OPTS -XX:GCTimeRatio=19" # 目标吞吐量比率 (1 / (1 + ratio))
CMS (Concurrent Mark Sweep):低延迟的践行者
CMS 收集器专为互联网 B/S 架构设计,核心目标是获取最短的 GC 停顿时间,以提升用户体验。它基于标记-清除算法,其执行周期分为四个核心阶段:
- 初始标记 (STW):仅标记 GC Roots 直接关联的对象,速度极快。
- 并发标记:从 GC Roots 出发遍历整个对象图,与用户线程并发执行。
- 重新标记 (STW):修正并发标记期间因用户线程运行导致的对象引用变化,停顿时间略长于初始标记,但远短于并发标记。
- 并发清除:清理废弃对象,与用户线程并发执行。

CMS 的局限性:
- CPU 资源敏感:并发阶段会占用部分 CPU 线程,导致应用程序整体吞吐量下降。
- 内存碎片:标记-清除算法不可避免地产生空间碎片,可能提前触发 Full GC。
- 浮动垃圾:并发清除阶段产生的新垃圾只能在下次 GC 时清理。
# 启用 CMS 收集器,并配置在 Full GC 后自动进行内存碎片整理
JAVA_OPTS="-XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection"
G1 (Garbage-First):现代化的 Region 内存模型
G1 是一款面向服务端应用的高性能收集器,旨在替代 CMS。它打破了传统物理分代的界限,将整个堆内存划分为多个大小相等的独立区域(Region)。

每个 Region 都可以动态充当 Eden、Survivor 或 Old 区域。这种设计使得 G1 能够更灵活地管理内存,并建立可预测的停顿时间模型。此外,G1 专门划分了 Humongous 区域 来存储大小超过 Region 容量 50% 的巨型对象,避免了大对象在老年代中频繁复制带来的性能损耗。

G1 的垃圾回收模式
- Young GC:当 Eden 区域耗尽时触发,仅回收新生代 Region。存活对象被复制或晋升。
- Mixed GC:当老年代占用率达到设定阈值(
InitiatingHeapOccupancyPercent)时触发。它不仅回收所有新生代 Region,还会根据停顿时间目标,智能选择部分回收价值最高的老年代 Region 进行回收。 - Full GC:当内存分配速度远超回收速度,导致堆空间耗尽时触发。G1 的 Full GC 会退化为单线程的 Serial Old 模式,造成极长的 STW,在生产环境中应通过调优极力避免。
G1 实战代码与参数调优
以下 Java 代码演示了如何通过分配大对象来触发 G1 的 Humongous 区域分配机制:
public class G1HumongousAllocationTest {
// 假设 JVM 启动时配置 -XX:G1HeapRegionSize=2M
// 当对象大小超过 1MB (2M的50%) 时,即被视为巨型对象
private static final int ALLOCATION_SIZE = 1024 * 1024 * 2; // 2MB
public static void main(String[] args) {
System.out.println("Initiating Humongous object allocation...");
// 分配巨型对象,G1 会直接将其放入 Humongous Region (属于老年代的一部分)
byte[] giantPayload = new byte[ALLOCATION_SIZE];
// 保持强引用,模拟业务逻辑中的大缓存或大报文
System.out.println("Payload size: " + giantPayload.length + " bytes");
}
}
针对 G1 收集器的核心调优参数配置参考:
| JVM 参数 | 功能描述 |
|---|---|
-XX:+UseG1GC |
启用 G1 垃圾收集器。 |
-XX:MaxGCPauseMillis=200 |
设置期望的最大 GC 停顿时间目标,G1 会据此调整回收的 Region 数量。 |
-XX:G1HeapRegionSize=4M |
手动指定 Region 大小(1M~32M,必须是 2 的幂)。影响巨型对象的判定阈值。 |
-XX:InitiatingHeapOccupancyPercent=45 |
触发并发标记周期(进而触发 Mixed GC)的堆占用率阈值,默认 45%。 |
-XX:G1NewSizePercent=20 |
新生代可动态调整的最小百分比。 |
-XX:G1MaxNewSizePercent=60 |
新生代可动态调整的最大百分比。 |