Java虚拟机的垃圾回收器
如何查看默认的垃圾回收器:
java
java -XX:+PrintCommandLineFlags -version

垃圾回收器的种类:
| 回收器 | 作用区域 | 适用场景 | 核心启动参数 |
|---|---|---|---|
| Serial | 新生代 | 客户端模式,资源受限环境 | -XX:+UseSerialGC |
| Serial Old | 老年代 | 同 Serial;CMS 异常时的兜底回收器 | 随 Serial GC 自动启用 |
| Parallel Scavenge | 新生代 | 后台批处理、大数据计算、离线任务等对延迟不敏感的服务端场景 | -XX:+UseParallelGC |
| Parallel Old | 老年代 | 与 Parallel Scavenge 搭配,是 JDK 8 默认的 GC 组合 | 随 Parallel GC 自动启用(JDK7+) |
| ParNew | 新生代 | Web 应用、B/S 系统,JDK7/8 低延迟场景主流组合 | -XX:+UseParNewGC(需配合 CMS) |
| CMS | 老年代 | B/S系统,重视响应速度 | -XX:+UseConcMarkSweepGC |
| G1 | 全堆(划分为 Region) | 大型服务端应用,JDK9 + 默认 GC | -XX:+UseG1GC |
| ZGC | 全堆(无分代) | 超大堆,极致低延迟,JDK15 正式发布 | -XX:+UseZGC |
| Shenandoah | 全堆(无分代) | 红帽系 JDK 环境、超低延迟需求场景,JDK17 正式发布 | -XX:+UseShenandoahGC |
为什么有这么多种垃圾回收器?
Java 虚拟机规范仅定义了垃圾回收的核心目标,但未约束具体实现方式(如回收算法、线程模型、分代策略等),这使得 Oracle、RedHat 等不同厂商,以及不同版本的 JVM 可根据技术趋势自主设计回收器。垃圾回收的吞吐量、延迟、内存占用三大核心指标无法同时做到极致。Java 应用覆盖面广,不同场景对 GC 的需求差异显著,随着硬件升级和应用需求升级,GC 技术持续迭代,从早期单线程的 Serial,到多线程的 Parallel/CMS,再到全堆的 G1,直至超低延迟的 ZGC/Shenandoah,本质是适配不断变化的技术环境。
在介绍各个垃圾回收器前请先了解下面两个名词的具体含义:
并发(Concurrency):指多个任务在同一时间段内交替执行,它们共享同一个 CPU 核心,通过时间片轮转等调度机制切换执行。
并行(Parallelism):指多个任务在同一时刻同时执行,必须依赖多 CPU 核心或多处理器,每个核心独立处理一个任务,不存在任务切换的开销。
下面进行逐个垃圾回收器的具体介绍:
Serial GC
串行收集器(Serial GC) 是 Java 虚拟机垃圾回收体系中最基础、最经典的回收器组合,也是早期 JVM Client 模式下的默认垃圾回收方案。该回收器采用分代回收协同机制,由负责新生代垃圾回收的 Serial 收集器,与负责老年代垃圾回收的 Serial Old 收集器两个核心组件构成。二者均遵循单线程、独占式的核心设计原则,全程以单个回收线程执行垃圾清理操作,并依赖 Stop-The-World(STW)机制暂停所有用户线程以保证内存操作的原子性,在资源受限的特定场景下,仍具备不可替代的轻量化优势。
在串行收集器进行垃圾回收时,Java应用程序中的线程都需要暂停,等待垃圾回收的完成。如图所示,在串行回收器运行时,应用程序中的所有线程都停止工作,进行等待。

新生代串行回收器(Serial)
新生代串行回收器(Serial)的核心回收算法为复制算法,其设计匹配新生代对象朝生夕死、存活占比极低的核心特征。
算法核心流程:将新生代划分为 Eden 区与两个大小相等的 Survivor 区(S0、S1),回收时先标记 Eden 区与当前使用的 Survivor 区中存活的对象,再将这些少量存活对象复制到空闲的 Survivor 区,最后直接清空原 Eden 区与已使用的 Survivor 区,完成内存回收。
算法适配优势:
- 效率层面:仅需拷贝少量存活对象,避免了对大量垃圾对象的逐个清理,配合单线程执行模式,无需额外处理线程切换、锁同步等开销,逻辑处理高效;
- 内存层面:复制后存活对象在目标 Survivor 区连续排列,避免了内存碎片问题,保障后续对象分配时的内存连续性;
- 场景适配性:在单核 CPU、资源受限的硬件环境中,Serial 回收器的单线程优势可充分发挥 ------ 无多核线程调度损耗,其 GC 效率甚至优于并行回收器、并发回收器等依赖多核的回收器。
老年代串行回收器(Serial Old)
老年代串行回收器(Serial Old)采用标记 - 压缩算法,其设计匹配老年代对象存活率高、生命周期长、回收频率低的核心特征,同时兼顾老年代内存空间大的特性。
算法核心流程分为三个阶段:标记阶段,遍历老年代内存,标记所有存活对象;压缩阶段,将所有存活对象向内存空间的一端移动,保证存活对象的内存连续性;清理阶段,清空内存末端的空闲区域,完成回收。
算法特性:
- 内存层面:标记 - 压缩算法通过移动存活对象与连续排列的设计,解决了老年代的内存碎片问题,避免因碎片导致的大对象分配失败;
- STW 影响:由于老年代内存空间通常较大,且存活对象占比高,Serial Old 执行回收时的 STW 停顿时间会显著长于新生代 Serial 回收器,在堆空间较大的应用中,可能出现明显的应用停顿;
- 兼容性优势:Serial Old 具备良好的兼容性,可与新生代的 Serial、ParNew 等回收器配合使用,同时也是 CMS 回收器发生 "Concurrent Mode Failure"(并发回收失败)时的兜底降级回收器,保障 JVM 内存回收的稳定性。
示例代码:
java
import java.util.Random;
public class GCDemo {
public static void main(String[] args) {
System.out.println("=====GCDemo,Hello====");
try {
String str = "GCDemo";
while (true){
str += str + new Random().nextInt(77777777) + new Random().nextInt(88888888);
str.intern();
}
}catch (Throwable e){
e.printStackTrace();
}
}
}
使用该参数运行代码 -XX:+UseSerialGC -XX:+PrintGCDetails
需要注意的是一旦指定 -XX:+UseSerialGC 参数,JVM 会同时启用新生代 Serial 回收器和老年代 Serial Old 回收器,形成 Serial + Serial Old 的固定组合,无需单独配置 Serial Old

首次 GC 输出:
GC (Allocation Failure) \[DefNew: 60261K-\>7665K(74944K), 0.0182660 secs\] 60261K-\>17491K(241536K), 0.0183354 secs\] \[Times: user=0.00 sys=0.00, real=0.02 secs
下面进行具体解释:
-
GC (Allocation Failure)
GC:表示这是 Minor GC(仅回收新生代),若为 Full GC 则是回收新生代和老年代;
Allocation Failure:GC 触发原因 ------ Eden 区剩余空间不足以分配新对象,是 Minor GC 最常见的触发原因。
-
DefNew: 60261K-\>7665K(74944K), 0.0182660 secs
DefNew:Serial 新生代收集器的标识(Default New Generation),确认 -XX:+UseSerialGC 生效;
60261K:GC 前新生代(Eden+From Survivor)已使用的内存;
7665K:GC 后新生代(To Survivor)剩余的存活对象内存;
74944K:新生代总内存大小(Eden + 2×Survivor 的总容量);
0.0182660 secs:新生代单独的 GC 耗时(标记 + 复制 + 清空,约 18.27 毫秒)。
-
60261K->17491K(241536K), 0.0183354 secs
60261K:GC 前整个 JVM 堆已使用的内存;
17491K:GC 后整个 JVM 堆已使用的内存;
241536K:整个 JVM 堆的总内存大小;
0.0183354 secs:本次 GC 全程(含新生代 + 堆整体处理)的总耗时(约 18.34 毫秒)。
-
Times: user=0.00 sys=0.00, real=0.02 secs
user:GC 线程自身执行业务逻辑消耗的 CPU 时间(0 秒,因数值过小被四舍五入);
sys:是操作系统内核为 GC 线程提供服务的 CPU 耗时(如内存申请、线程切换,同样四舍五入为 0);
real:从 GC 开始到结束的实际耗时,即 STW 时长。
串行回收器的优缺点
串行回收器的优点
- 内存与 CPU 资源开销最低
串行回收器仅依赖单个 GC 线程完成全部回收工作,无需分配额外内存维护多线程任务队列、线程池状态及同步锁结构;同时避免了多线程调度、锁竞争与任务拆分 / 合并带来的 CPU 资源消耗,是所有垃圾收集器中资源占用最小的类型。 - 无多线程同步相关的额外开销
单线程执行模式下,回收过程中不存在多线程间的协作与数据竞争问题,无需进行锁的获取与释放操作,消除了多线程 GC 因同步机制产生的性能损耗,保障了回收过程的纯粹性与高效性。 - 实现逻辑简洁,稳定性极强
串行回收器的代码路径是所有垃圾收集器中最短、最清晰的,无需处理多线程的复杂边界情况(如线程死锁、任务分发失败、结果合并异常等),故障触发点极少,在运行过程中具备极高的稳定性。 - 老年代回收后无内存碎片
老年代 Serial Old 收集器采用标记 - 整理算法,回收时会将所有存活对象向内存空间的一端进行紧凑排列,然后清理边界外的全部内存。回收后的老年代内存地址连续,不存在内存碎片,可直接通过指针碰撞法完成新对象的高效分配。 - GC 日志清晰易懂,无多线程干扰
回收过程的日志输出仅反映单个 GC 线程的执行轨迹,包含触发原因、内存变化、耗时统计等核心信息,无多线程任务的交错日志,便于精准追溯回收过程的完整链路。
串行回收器的缺点
- 全程触发 STW,暂停时长与堆内存大小呈正相关
回收过程中会强制暂停所有用户线程,直到回收操作完全结束。STW 时长与堆内存总容量、堆内存活对象数量线性相关 ------ 堆内存越大、存活对象越多,单线程完成标记、复制 / 整理的工作量越大,用户线程的暂停时间越长。 - 无法利用多核 CPU 的并行处理能力
设计上仅支持单个 GC 线程执行回收操作,无论宿主机器的 CPU 核心数多少,回收过程中仅有一个核心参与工作,其余 CPU 核心处于闲置状态,完全浪费了多核架构的性能优势。 - 扩展性极差
随着堆内存容量的增加,回收效率会急剧下降。当堆内存超过一定阈值后,STW 时长会呈指数级增长,无法通过扩大堆内存容量来满足更高的内存需求,不具备横向扩展的能力。 - Full GC 执行耗时远高于 Minor GC
当触发 Full GC 时,需同时完成新生代的标记 - 复制与老年代的标记 - 整理操作,单线程需处理全堆对象。由于老年代对象存活率远高于新生代,标记 - 整理的工作量远大于标记 - 复制,导致 Full GC 的耗时通常为 Minor GC 的数倍甚至数十倍。 - 回收效率完全受制于单线程性能上限
回收速度的提升仅能依赖单个 CPU 核心的运算能力优化,无法通过增加 CPU 核心数来提升回收效率,存在明显的性能天花板。
ParNew GC
ParNew 回收器是一款新生代专用、多线程并行、独占式的垃圾回收器。其本质是对新生代串行回收器(Serial GC)的多线程化改造,在回收策略、核心算法及参数体系上与新生代串行回收器完全一致。
ParNew 回收器属于独占式 GC,执行垃圾回收时会触发全局 Stop-The-World(STW)机制,暂停所有用户线程。在多核 CPU 等并发能力较强的硬件环境中,多线程并行执行标记、复制操作可有效分摊回收任务,显著缩短新生代 GC 的 STW 时长,性能优于串行回收器;而在单 CPU 或并发能力较弱的系统中,多线程调度产生的上下文切换开销会抵消并行回收的收益,其实际回收效率不仅无法超越串行回收器,甚至可能因线程竞争压力导致表现更差。
ParNew 回收器的核心设计目标是降低新生代 Minor GC 的 STW 时间,且为老年代并发标记清除回收器(CMS)提供低延迟的新生代回收支撑,二者组合形成新生代并行回收与老年代并发回收的低延迟 GC 架构,是 JDK 8 及之前低延迟业务场景的核心 GC 解决方案。
ParNew 回收器的工作示意图如图所示

使用该参数执行:-XX:+UseParNewGC -XX:+PrintGCDetails

-XX:+UseParNewGC 即新生代使用 ParNew 回收器,老年代使用串行回收器
使用该参数执行:-XX:+UseConcMarkSweepGC -XX:+PrintGCDetails

-XX:+UseConcMarkSweepGC 即新生代使用 ParNew 回收器,老年代使用 CMS 回收器
ParNew 回收器并行线程数配置
ParNew 回收器的并行回收线程数由 JVM 参数 -XX:ParallelGCThreads=N 显式指定,该参数直接决定新生代 GC 的并行执行效率。线程数的合理配置需匹配硬件 CPU 核心数:在多核环境下,线程数通常建议与 CPU 核心数相当,以充分利用硬件并行能力;若线程数过多,会引发频繁的线程上下文切换,反而抵消并行回收的性能收益,降低垃圾收集效率。
ParNew 回收器的线程数默认规则分两种场景:
当 CPU 核心数 ≤ 8 时,ParallelGCThreads的默认值等于 CPU 核心数,实现线程与核心的 1:1 绑定,最大化并行回收效率;
当 CPU 核心数 > 8 时,ParallelGCThreads的默认值按公式计算:ParallelGCThreads = 3+((5*CPU_ Count)/8)
公式设计的底层逻辑
该公式的核心设计目标是平衡并行回收收益与硬件、软件层面的开销,而非简单的线性缩放,其参数设计深度耦合 ParNew 的应用场景与 GC 执行原理,具体拆解如下:
(1)基础线程数 "3":低延迟场景的兜底设计
ParNew 回收器的核心应用场景是搭配 CMS 老年代回收器,构建低延迟 GC 架构,公式中保留固定的 3 个基础线程,基于以下两点核心考量:
- 保障大规模 CPU 环境的基础并行能力:在 CPU 核心数极大的场景下,若仅按比例计算线程数,可能出现基础并行能力不足的问题。固定 3 个线程可确保新生代 GC 始终具备最低限度的并行执行能力,避免因纯比例缩放导致的回收效率下限过低。
- 适配 CMS 回收器的关键 STW 阶段需求:CMS 回收器的初始标记和重新标记阶段属于短时 STW 过程,这两个阶段的执行效率直接影响应用的延迟表现。而这两个阶段的并行能力依赖于 ParNew 的线程数配置,3 个基础线程可保障这两个低延迟关键阶段的最小并行效率,避免因线程数不足导致 STW 时间延长。
(2)比例系数 "5/8":硬件瓶颈与 GC 并行上限的双重约束
当 CPU 核心数超过 8 后,线程数不再与核心数线性匹配,而是按 5/8 的比例缩放,核心原因源于硬件与 GC 原理的双重限制:
- 硬件资源瓶颈约束:CPU 核心数超过 8 后,内存带宽、系统总线带宽、缓存一致性协议开销等硬件资源,会先于 CPU 核心数成为性能瓶颈。此时新增的 GC 线程无法获得足够的内存 IO 资源支撑并行回收,反而会因线程间竞争共享资源(如内存总线)导致效率下降。5/8 是 JVM 开发团队通过大量硬件压测得出的最优比例,可最大化硬件资源利用率,避免资源过载。
- GC 流程的并行上限约束:ParNew 基于复制算法实现新生代回收,其执行流程并非完全可并行化,存在不可避免的串行环节,例如全局安全点(SafePoint)等待、根节点枚举的初始串行校验等。当线程数超过 CPU 核心数的 5/8 后,串行环节的等待开销会超过并行回收带来的性能收益,导致整体 GC 效率不升反降,5/8 的比例恰好是并行收益与串行开销的平衡点。
补充说明:与 Parallel Scavenge 回收器的线程数规则差异
ParNew 回收器与 Parallel Scavenge 回收器虽共用 -XX:ParallelGCThreads 参数配置 GC 线程数,但两者的默认线程数计算规则存在本质差异。
从具体规则来看,当 CPU 核心数≤8 时,两者的默认线程数一致,均与 CPU 核心数相等,以此最大化多核并行效率;而当 CPU 核心数>8 时,Parallel Scavenge 则直接采用 (5×CPU核心数)/8的计算方式,没有固定的 3 个基础线程。
两者规则差异的核心逻辑在于设计目标的不同。ParNew 为低延迟场景而生,而 Parallel Scavenge 作为吞吐量优先的回收器,无需为低延迟预留基础线程,其设计逻辑完全围绕降低 GC 总耗时、提升 CPU 用于业务代码的占比展开,因此直接采用比例计算即可。
需要注意的是,实际调优时,线程数建议不超过 CPU 核心数的 80%,否则会因线程竞争加剧导致上下文切换开销剧增,反而降低 GC 效率。例如 32 核服务器中,ParNew 默认线程数为 23,Parallel Scavenge 默认线程数为 20,若需调整,可结合业务压测结果微调至 20-24 区间,且必须以实测的 GC 延迟、吞吐量数据作为最终判断依据。
Parallel GC
Parallel GC回收器核心定位
Parallel GC(并行垃圾回收器)是 Java 虚拟机中一款面向多核 CPU 架构、以吞吐量优先为核心设计目标的分代式并行独占型垃圾回收器,同时也是 JDK 8 及之前版本的默认垃圾回收方案。其设计初衷与核心价值在于:通过最大化利用多核 CPU 的并行计算能力执行回收操作,缩短垃圾回收总耗时在系统运行时间中的占比,提升用户业务代码运行时间的占比,从而实现吞吐量优化目标,保障业务执行效率。
如何理解吞吐量
ParallelGC 核心设计导向明确为吞吐量优先,垃圾回收场景下的吞吐量计算公式为:
吞吐量 = 用户代码运行时间 /(用户代码运行时间 + 垃圾收集时间)
直观反映了系统资源在业务逻辑执行与垃圾回收开销之间的分配比例,核心衡量业务代码对系统资源(尤其是CPU)的有效利用效率。
具体示例说明:若某程序累计运行时长为100分钟,其中垃圾收集环节耗时1分钟,代入公式计算可得吞吐量为(100-1)/100×100% = 99%。这意味着该程序运行周期内,99%的时间用于执行核心业务逻辑,仅1%的时间用于垃圾回收开销。
高吞吐量的适用场景:
对于前台交互类业务,垃圾收集导致的进程停顿会直接影响用户操作响应速度,降低用户体验,因此高吞吐量并非此类业务的首要目标;
而对于后台非交互类任务(如科学计算、大数据ETL、批量数据处理),任务执行效率优先于瞬时响应速度,短暂的垃圾收集停顿不会对业务产生直接负面影响,高吞吐量可充分利用CPU资源,缩短任务整体执行周期,提升运行效率。
需要注意的是,吞吐量的定义与衡量维度会随观测层面不同而存在差异:
- JVM层面的吞吐量:特指单个JVM进程运行周期内,用户业务代码有效执行时间占进程总运行时间的比例。
其关注粒度聚焦于单进程内部的资源(CPU、内存)利用效率,核心评估垃圾回收机制等内存管理组件对进程运行效率的影响,本质是进程级别的资源利用率指标。 - 微服务层面的吞吐量:特指分布式微服务架构下,系统在单位时间内能够成功处理的业务请求数或完成的事务数。
其关注粒度上升至分布式系统全局的业务处理能力,核心评估整个微服务集群的协同处理效率,涉及跨服务调用、跨节点资源调度、负载均衡等多个环节,本质是系统级别的业务处理能力指标。
Parallel GC 工作原理
从架构属性来看,Parallel GC 属于分代式回收器,其工作原理完全依托 JVM 堆的新生代和老年代划分,且不同代际配套差异化的回收算法与多线程执行机制:
-
新生代回收组件为 Parallel Scavenge,采用复制算法,依托多线程并行执行「标记 - 复制 - 清理」操作。当 Eden 区内存分配失败时触发 Minor GC,仅需复制少量存活对象至空闲 Survivor 区,具备回收效率高、内存无碎片的优势,适配新生代对象生命周期短、回收频率高的特点。
-
老年代回收组件为 Parallel Old,采用标记 - 压缩算法,基于多线程并行执行「标记 - 压缩 - 清除」操作。当新生代对象晋升老年代失败、老年代内存阈值触达等场景触发 Major GC/Full GC,通过标记存活对象、压缩整理存活对象至内存一端、释放空闲空间,既保证老年代大对象的高效回收,又能避免内存碎片产生,适配老年代对象存活时间长、占用空间大的场景。

Parallel GC 核心运行特性
从运行特性来看,ParallelGC 属于并行独占型垃圾回收器,核心设计思路是以短暂 STW 换取高回收效率,是其实现吞吐量优先设计目标的底层支撑,该特性贯穿新生代 Parallel Scavenge 与老年代 Parallel Old 两个回收组件的全流程。
-
并行回收:多核 CPU 资源的最大化利用
在垃圾回收触发时,JVM 会启动多个 GC 线程并行执行标记、复制、整理等核心任务,线程数默认与 CPU 核心数保持一致,可通过参数 -XX:ParallelGCThreads 手动调整,以此适配不同服务器的硬件资源。这种多线程并行机制同时作用于新生代和老年代的回收过程,相较于单线程回收器,能够大幅压缩单次垃圾回收的总耗时,尤其在多核 CPU 架构下优势更为明显。
-
独占式执行:全程触发 Stop-The-World(STW)
从回收任务启动到执行完毕的全周期内,JVM 会暂停所有用户线程,不存在用户线程与 GC 线程并发运行的阶段,直至垃圾回收完成后,用户线程才会恢复执行。这一特性意味着 ParallelGC 的回收过程会暂停所有用户线程,直至 GC 任务完全结束。与 CMS、G1 等支持并发回收的垃圾回收器形成本质区别,也是其无法满足低延迟业务场景需求的核心原因。
Parallel GC回收器的启用
在 JDK 8 及以后版本中,-XX:+UseParallelGC 和 -XX:+UseParallelOldGC 具备互相激活的特性,即配置其中任意一个参数,另一个会被自动连带激活。原因在于 JDK8 + 将新生代的 Parallel Scavenge 与老年代的 Parallel Old 绑定为一套完整的吞吐量优先回收方案,默认作为 JVM 的标配组合。启用 Parallel Scavenge 时,老年代会自动切换为 Parallel Old,启用 Parallel Old 时,新生代也会自动切换为 Parallel Scavenge。
演示代码:
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseParallelGC

使用 -XX:+UseParallelOldGC 的日志如下:

Parallel GC 吞吐量调优核心参数与自适应策略
Parallel GC 提供了两组核心参数用于精准调控系统吞吐量,同时配套自适应调优策略,以平衡垃圾回收停顿时间与吞吐量的关系,实现自动化参数调优。
-XX:MaxGCPauseMillis:最大垃圾收集停顿时间目标参数
该参数用于设置垃圾回收单次停顿的目标最大值,取值为大于 0 的整数,单位为毫秒。需要明确的是,这是一个指导性目标参数而非强制参数,JVM 会通过动态调整堆内存大小等核心参数,尽可能将单次 GC 的 STW 时间控制在该阈值以内,但不保证绝对满足。
其底层调优逻辑存在明确的权衡关系:若将该参数值设置过小,为缩短单次回收耗时,JVM 会主动缩减堆内存空间(小堆内存中待回收对象更少,回收速度更快);但堆空间缩小会导致垃圾回收触发频率大幅提升,进而增加 GC 总耗时在系统运行时间中的占比,最终降低系统吞吐量。反之,适当调大该参数值,JVM 可分配更大的堆空间,减少 GC 触发频率,有助于提升吞吐量,但单次 GC 的停顿时间可能会相应延长。
-XX:GCTimeRatio:吞吐量比例控制参数
该参数用于定义系统吞吐量的目标比例,取值为 0 到 100 之间的整数。其吞吐量计算遵循公式:GC耗时占比 = 1/(1+n)(其中 n 为该参数的取值),对应的系统吞吐量目标即为 1 - GC耗时占比。
例如,当参数值设为 19 时,GC 耗时占比为 1/(1+19)=5%,系统目标吞吐量则为 95%;该参数的默认值为 99,对应 GC 耗时占比不超过 1/(1+99)=1%,系统目标吞吐量不低于 99%。JVM 会基于该目标,动态调整堆内存分区大小、GC 触发阈值等参数,确保 GC 总耗时占比稳定在目标范围内。
-XX:+UseAdaptiveSizePolicy:自适应 GC 调节策略
ParallelGC 支持自适应 GC 调优策略,该策略可通过参数 -XX:+UseAdaptiveSizePolicy 开启(JDK 8 及以后版本默认开启)。
在该模式下,JVM 会摒弃手动配置的新生代大小、Eden 区与 Survivor 区的比例、对象晋升老年代的年龄阈值等细节参数,转而根据系统运行状态,自动动态调整上述参数,以在堆内存大小、吞吐量、单次 GC 停顿时间三者之间找到最优平衡点。该策略的核心价值在于降低手动调优的复杂度,适用于堆内存需求波动较大、手动调优难度高的场景 ------ 用户仅需指定虚拟机最大堆内存、目标吞吐量(-XX:GCTimeRatio)和目标停顿时间(-XX:MaxGCPauseMillis),即可交由 JVM 完成全流程的参数调优工作。
以及前面提到过的,JVM 的 GC 线程数默认与 CPU 核心数保持一致,可通过参数 -XX:ParallelGCThreads 手动调整,以此适配不同服务器的硬件资源。
Parallel GC 与多线程回收器 ParNew 的本质区别:
Parallel GC 与同属多线程回收器的 ParNew GC 存在明确的功能定位差异。二者虽均支持多线程并行回收,但 ParNew 的设计初衷是为低延迟回收器 CMS 提供新生代并行回收能力,自身无法独立完成全堆回收,必须与 CMS 配合使用;而 Parallel GC 是一款独立的全堆回收器,无需依赖其他 GC 组件即可完成新生代与老年代的回收任务,且其设计目标具有唯一性,即通过优化回收效率实现系统吞吐量最大化。Parallel GC 与 ParNew 回收器的核心差异之一,在于其支持自适应 GC 调优策略,该策略可通过参数 -XX:+UseAdaptiveSizePolicy 开启。
Parallel GC 与 ParNew GC的本质区别
Parallel GC 与 ParNew 虽同属 JVM 多线程并行垃圾回收器,但二者在核心定位、功能边界、设计目标及配套能力上存在本质差异,具体可从以下维度清晰区分:
- 从功能定位与独立性来看,二者的核心价值导向截然不同。
ParNew 回收器的设计初衷是为低延迟回收器 CMS 提供新生代并行回收能力,其功能边界仅限于新生代,自身不具备老年代回收能力,无法独立完成全堆垃圾回收任务,必须与 CMS 配合使用。而 ParallelGC 是一款独立的全堆并行回收器,无需依赖任何其他 GC 组件,新生代由 Parallel Scavenge 组件负责,老年代由 Parallel Old 组件负责,可自主完成全堆垃圾回收任务,设计目标是通过多核并行回收提升效率、降低 GC 总耗时占比,最终实现系统吞吐量最大化。 - 从调优能力来看,自适应 GC 调优策略是 ParallelGC 区别于 ParNew 的核心特性之一。
ParallelGC 支持通过参数 -XX:+UseAdaptiveSizePolicy 开启自适应调优机制,在该模式下,JVM 会根据预设的吞吐量目标和最大停顿时间目标,自动动态调整核心参数,在堆内存大小、GC 停顿时间与吞吐量三者间实现最优平衡,大幅降低手动调优的复杂度。而 ParNew 回收器不具备自适应调优能力,其新生代内存分区比例、对象晋升阈值等参数均需依赖用户手动配置,调优灵活性与自动化程度远低于 ParallelGC。
CMS垃圾回收器
CMS(Concurrent Mark Sweep:并发标记清除)是JVM 专为老年代设计的并发式垃圾回收器,其首要优化目标为最小化应用线程的 Stop-The-World(STW)停顿时间。
CMS 的核心执行流程
CMS 垃圾回收器的核心执行流程基于 标记 - 清除 算法构建,核心设计思路为:将回收流程中耗时占比最高的两个核心阶段(并发标记、并发清除)与用户业务线程并行执行,仅在初始标记、重新标记两个关键节点触发短时 Stop-The-World(STW,即暂停所有用户线程),实现最小化应用停顿时间的核心目标。
执行流程包括初始标记(Initial Mark)、并发标记(Concurrent Mark)、重新标记(Remark)、并发清除(Concurrent Sweep),如图:

在 HotSpot JVM 的工程化完整实现中,CMS 的执行流程拓展为六阶段,包括初始标记、并发标记、预清理、重新标记、并发清除和并发重置,且贯穿所有阶段的核心设计原则保持一致:仅初始标记、重新标记两个阶段触发 STW,其余四个阶段均与用户线程并发执行,既保障了垃圾回收的准确性与完整性,又维持了低停顿的设计初衷,如图:

HotSpot JVM 中 CMS 垃圾回收器实现流程的各阶段执行逻辑如下:
-
初始标记(Initial Mark)
该阶段会暂停所有用户线程,核心操作是扫描并标记与 GC Roots 直接关联的老年代存活对象,例如虚拟机栈本地变量引用的老年代对象、方法区静态变量 / 常量引用的老年代对象,此过程不会遍历对象的完整引用链。由于扫描范围极小,该阶段耗时通常在毫秒级,对应用的影响几乎可忽略,其输出的根存活对象集合是后续并发标记的起点。
-
并发标记(Concurrent Mark)
此阶段 GC 线程与用户线程完全并行运行,不触发 STW。GC 线程从初始标记得到的根存活对象集合出发,递归遍历老年代中所有对象的引用链,标记所有可达的存活对象。该阶段是整个 CMS 流程中耗时最长的环节,可能达到秒级,但因与用户线程并发执行,应用不会出现卡顿。需要注意的是,并发标记阶段存在标记偏差隐患:用户线程在运行过程中可能会修改对象的引用关系,导致部分存活对象未被标记或部分垃圾对象被错误标记为存活,这一问题需要后续阶段修正。
-
预清理(Preclean)
该阶段是对并发标记的补充优化,核心操作包含两部分:一是处理并发标记阶段产生的"脏卡",即 Card Table 中记录了对象引用关系变化的内存卡片,通过更新这些脏卡对应的对象标记状态,减少后续重新标记的工作量;二是提前处理并发标记期间新生代晋升到老年代的对象,以及老年代内新分配的大对象,避免这些对象在重新标记阶段消耗额外的扫描时间。预清理阶段还包含一个可终止预清理子阶段,其设计目的是防止预清理无限期执行,当预清理时长达到阈值,或新生代内存占用达到阈值时,JVM 会主动终止预清理,直接进入下一个阶段,避免过度占用 CPU 资源。
-
重新标记(Remark)
该阶段的核心目标是修正并发标记与预清理阶段因用户线程操作导致的标记偏差,确保老年代存活对象标记的准确性,执行时会再次触发 STW。为了最小化停顿时间,CMS 不会重新遍历老年代的所有对象,而是基于增量更新(Incremental Update)或原始快照(Snapshot At The Beginning, SATB)机制,仅扫描并发阶段中引用关系发生变化的对象。得益于预清理阶段的优化,该阶段的耗时虽略长于初始标记,但仍可控制在百毫秒级,对应用的影响处于可控范围。
-
并发清除(Concurrent Sweep)
此阶段 GC 线程与用户线程再次并行执行,核心操作是遍历整个老年代空间,识别并清除所有未被标记的垃圾对象,同时释放对应的内存空间。由于该阶段仅需检查对象的标记状态,无需遍历引用链,耗时比并发标记短,但仍属于耗时较长的环节。需要注意的是,并发清除阶段会产生"浮动垃圾"------ 用户线程在该阶段新产生的垃圾对象,因本次 GC 的标记过程已结束,无法被本次回收,只能留到下一次 GC 处理。因此 CMS 需预留一定内存空间,不能等到老年代完全占满时才触发回收,否则会引发 Concurrent Mode Failure,导致 JVM 退化为 Serial Old 收集器执行全量回收,造成长时间 STW。
-
并发重置(Concurrent Reset)
该阶段是 CMS 回收流程的收尾环节,核心操作是重置 CMS 收集器的内部数据结构,包括标记位图、Card Table、回收状态标志等,将这些数据结构恢复到初始状态,为下一次 CMS 回收周期做好准备。此阶段无业务逻辑相关操作,纯属于收集器的内部状态复位,与用户线程并发执行,对应用无任何感知,若跳过该阶段,下一次 CMS 回收将无法正常启动。
CMS 垃圾回收器核心参数配置及调优逻辑
CMS 作为面向 JVM 老年代的并发式垃圾回收器,其参数配置需围绕低停顿核心目标,兼顾 CPU 资源占用、回收触发时机、内存碎片治理三大维度,核心参数及调优逻辑如下:
(1)核心启用与并发线程数配置
- 启用参数:通过 -XX:+UseConcMarkSweepGC 可显式启用 CMS 回收器,该回收器为多线程执行模型,并发线程数的合理配置直接影响 GC 阶段对系统性能的干扰程度。
- 线程数默认规则:CMS 默认并发线程数计算公式为 (ParallelGCThreads + 3) / 4(ParallelGCThreads为 ParNew 的并行线程数参数);若需手工调整,可通过 -XX:ConcGCThreads 或 -XX:ParallelCMSThreads参数指定(二者功能等价,为不同版本 JVM 的兼容参数)。
- 线程数与性能关联:CMS 的并发特性是指 GC 线程与应用线程交替执行,而并行则指应用线程完全暂停、由多个 GC 线程同步执行 GC(并行回收器无并发特性)。当 CPU 资源紧张时,过多的 CMS 并发线程会与应用线程抢占 CPU 资源,导致 GC 阶段应用系统性能显著下降,需结合 CPU 核心数合理设定线程数。
(2)回收触发阈值配置(规避并发回收内存不足风险)
CMS 采用并发回收模式,回收过程中应用线程持续运行并持续产生新的垃圾(即浮动垃圾,指 CMS 本次回收周期内新产生的垃圾,无法被本次回收清理)。基于此特性,CMS 无法等待老年代内存完全饱和时触发回收,必须预留足够内存空间支撑应用在 GC 期间的正常运行,因此需通过阈值机制提前触发回收:
- 核心控制参数:-XX:CMSInitiatingOccupancyFraction 用于精准设定老年代内存使用率阈值,当老年代空间使用率达到该阈值时,JVM 将触发一次 CMS 回收;该参数默认值为 68(即老年代使用率达到 68% 时触发 CMS 回收)。
- 异常场景与核心风险:若应用程序老年代内存使用率增长速率过快,即便达到阈值触发 CMS 回收,在 CMS 执行过程中仍可能出现内存不足的情况,此时会触发 Concurrent Mode Failure(CMS 并发回收失败)。该异常发生时,JVM 会放弃低停顿的并发回收模式,退化为老年代串行收集器(Serial Old)执行全量垃圾回收;串行回收会触发长时间的 Stop-The-World(STW),应用程序将完全中断直至回收完成,导致业务停顿时间大幅增加。
- 参数调优逻辑:需结合应用程序的内存使用特征针对性调优
若应用老年代内存使用率增长缓慢,可适当提高该阈值,更高的阈值能有效降低 CMS 回收的触发频率,减少老年代 GC 次数,从而降低 GC 对应用性能的整体干扰;
若应用老年代内存使用率增长较快,需降低该阈值;更低的阈值可提前触发 CMS 回收,预留更多内存空间应对 GC 期间的浮动垃圾产生,避免因内存不足引发 Concurrent Mode Failure,进而规避串行回收导致的长时间 STW 风险。
(3)内存碎片治理参数(解决标记 - 清除算法固有问题)
CMS 针对老年代的回收基于标记 - 清除算法实现,而标记 - 清除算法的固有缺陷是仅清除垃圾对象但不整理内存空间:长期运行后会产生大量内存碎片(堆内存中形成离散的可用小空间,无连续的大块内存区域)。如图显示了CMS回收前后老年代的情况:

该问题会引发关键性能风险:即便老年代整体剩余内存空间充足,也可能因无法为大对象(如大数组、大实例对象)分配连续内存,被迫触发非必要的 Full GC,显著增加应用停顿时间。
为治理内存碎片,CMS 提供了专属的内存压缩整理参数,具体如下:
- 碎片整理开关:-XX:+UseCMSCompactAtFullCollection
开启该参数后,CMS 会在 Full GC 完成后执行内存碎片的压缩整理操作,将老年代中的存活对象移动至连续内存区域,整合离散的可用空间,消除内存碎片。需注意内存压缩整理操作无法与应用线程并发执行,会触发 Stop-The-World(STW),因此需结合业务场景权衡碎片治理需求与 STW 时长带来的性能影响。 - 整理频率控制:-XX:CMSFullGCsBeforeCompaction
该参数用于精准控制内存压缩整理的触发频率,设定连续执行多少次 CMS 回收后,触发一次内存压缩整理;其默认值为 0,即每次 Full GC 后均执行内存整理。
若业务可接受一定程度的碎片累积,可适当提高该值,减少内存整理的触发频率,降低 STW 的发生次数;若业务频繁分配大对象、碎片累积速度快,需维持较低值(或保留默认值 0),避免因碎片过度累积导致频繁触发 Full GC。
CMS日志
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC

以上信息是一次CMS收集的输出,可以看到,在CMS回收器的工作过程中,包括了初始化标记、并发标记、预清理、重新标记、并发清理和重发重置等几个重要阶段。在日志中,还可以看到CMS的耗时以及堆内存信息。
G1垃圾回收器
G1(Garbage-First)是 Java HotSpot 虚拟机中一款面向服务端应用、兼顾低延迟与高吞吐量的垃圾回收器。该收集器在 JDK 9 及后续版本中取代 CMS,成为默认垃圾回收器。其核心创新在于通过分区化内存管理、垃圾优先回收策略与停顿预测模型,在可控停顿时间内最大化内存回收效率,尤其适配数十 GB 级别大堆内存的应用场景。
从设计定位来看,G1 的长期目标是替代 CMS 收集器,其核心优势在于解决了 CMS 内存碎片化与回收停顿时间不可控问题,同时完整保留了 CMS 的并发标记核心特性,是现代大型 Java 应用的首选垃圾回收方案。
核心特性
G1 垃圾回收器的核心特性可精准归纳为:
- 并行性:回收核心阶段由多 GC 线程并行执行,充分利用多核 CPU 算力,最大化缩短 Stop-The-World(STW,全停顿)时间。
- 并发性:G1 对老年代存活对象的核心标记阶段采用"并发执行"模式,即标记过程与应用线程同时运行,仅在关键节点触发极短时间的 STW,避免全程阻塞应用线程。
- 逻辑分代:G1打破传统垃圾回收器将堆物理划分为连续新生代、老年代的模式,采用区域化布局,堆内存被划分为多个大小相等的独立 Region,Region无物理上的新生代/老年代边界,而是通过逻辑动态分配角色,单个Region可动态承担Eden区、Survivor区、老年代区的职责,还专门设有Humongous区,通过动态标记 Region 角色实现逻辑分代,兼顾分代回收效率与内存使用灵活性;
- 空间自整理:回收Region时,会将该Region内的存活对象复制到其他空闲Region中,复制过程中会将对象紧凑排列在新 Region 中,原 Region 标记为空闲并归还堆。这一过程同时完成了垃圾清理与内存压缩,避免了内存碎片的产生。
- 垃圾优先回收策略:基于 Region 回收收益(回收该 Region 可释放的内存量 ÷ 回收该 Region 所需耗时)排序,优先回收收益最高的 Region,在可控停顿时间内最大化内存回收效率;
- 可预测停顿:G1基于历史回收数据建立预测模型,精准估算每个Region的回收耗时,动态筛选Region组合进行回收,确保总停顿时间不超过设定阈值;CMS的停顿时间(尤其是并发标记失败时的Full GC)不可控,而G1通过选择性回收Region的方式,将停顿时间稳定在用户可接受范围。
Region 的基础划分规则
Region(区域)是G1垃圾回收器的最小内存管理与回收单元,其核心设计是将Java堆拆分为若干物理离散、大小均等的内存块。以下是Region基础划分的完整规则与设计逻辑:
- 核心大小规则:
- 大小范围固定:单个Region的大小必须落在 1MB~32MB 区间内,这是G1通过大量实践验证的最优区间;小于1MB会导致Region数量过多(管理成本增加),大于32MB会使回收粒度过大(停顿时间不可控);
- 必须为2的幂:Region大小必须是2的整数次幂,设计原因是利用位运算快速计算内存地址、对象归属Region等,减少CPU计算开销;
- 默认自适应计算:JVM启动时,会根据Java堆总大小自适应推导Region大小,核心目标是让整个堆的Region总数接近2048个;
- 手动指定规则:可通过JVM参数 -XX:G1HeapRegionSize=n 手动指定Region大小,但必须严格符合 1MB~32MB 且为 2的整数次幂的规则(否则JVM启动时会报错);建议优先使用默认自适应机制,仅在特殊场景(如应用存在大量固定大小的大对象)下微调。
- 容量上限规则:
- 理论上限计算:单个Region最大32MB,最多支持2048个Region,因此理论上G1可管理的最大堆内存为 32MB × 2048 = 64GB;
- 实际适配范围:64GB是理论极限,实际生产环境中G1最优适配范围是 4GB~64GB;堆内存小于4GB时,Region数量过少,回收粒度变大,难以精准控制停顿时间;堆内存超过64GB时,需通过调整JVM参数突破限制,但会导致管理效率下降;
- 上限的设计意义:2048个Region是G1后台线程的高效管理阈值,超过此数量会导致后台统计、优先级排序等操作的CPU开销激增,反而降低整体性能。
- 核心特性规则:
- 物理离散分布:所有Region在Java堆中是物理上不连续的内存块,这种设计让G1可按需选择任意Region组合进行回收,为垃圾优先策略提供可能;
- 对象存储逻辑连续:G1不要求对象的物理存储连续,仅需保证逻辑连续(即对象的引用关系完整),唯一例外是Humongous对象(大小超过单个Region一半的对象),这类对象必须存储在连续的多个Region中(称为Humongous Region),避免大对象跨多个离散Region存储导致的引用追踪复杂、访问效率低下;
- 功能角色动态切换:单个Region的代际角色(如Eden区、Survivor区、老年代区)并非固定,而是根据应用的内存分配需求动态切换。例如:一个空闲Region可被分配为Eden区承载新对象,经历Young GC后若存活对象晋升,该Region可转为老年代区;老年代Region被回收后变为空闲Region,又可重新分配为Eden区。这种动态切换机制大幅提升了内存利用率,让G1能自适应应用的动态内存分配特性(如突发大量新对象创建时,可快速将多个空闲Region转为Eden区)。
Region区域化管理的核心优势:
- 规避全堆扫描/回收:将整个堆内存拆分为独立的Region单元,垃圾回收时无需扫描或回收全堆,仅需针对待回收Region集合(CSet)操作,大幅降低垃圾回收的工作量与STW(Stop-The-World,全停顿)时长;
- 解除连续内存依赖:Region为物理不连续的内存块,无需保证年轻代、老年代的物理连续性,解决传统分代回收器中大堆内存分配时连续空间不足的问题;
- 支持动态分代适配:单个Region的功能角色(年轻代/老年代)可动态切换,无需在JVM启动时固定分代内存大小,能根据应用程序的内存分配特性自适应调整,提升内存利用率。

G1垃圾回收器堆内存划分
-
新生代 Region
- 角色定位:新生代Region是G1承载新创建对象、执行高频轻量回收的核心区域,由若干离散Region动态组成,逻辑上分为Eden区和Survivor区(S0、S1),无物理连续要求。
- 回收逻辑:新生代回收仅触发Young GC,全程STW且多线程并行执行。
Eden Region总空间耗尽,无法容纳新对象,触发新生代回收(Young GC),全程STW且多线程并行执行;GC线程并行扫描所有Eden Region和当前活跃的Survivor Region(如S0),标记存活对象;存活对象优先复制到另一块空闲的Survivor Region(如S1),同时对象年龄计数器+1;若对象年龄达到晋升阈值(默认15,可通过-XX:MaxTenuringThreshold调整),直接晋升至老年代Region;若Survivor Region空间不足,部分存活对象会直接溢出至老年代Region,无需等待年龄阈值。回收完成后,原Eden Region和活跃Survivor Region(S0)全部标记为空闲Region,等待重新分配角色;新的Survivor Region(S1)成为下一次Young GC的活跃区。
-
老年代 Region
- 角色定位:老年代Region承载存活时间长、稳定性高的对象,同样由离散Region组成,其角色一旦确定,需通过Mixed GC或Full GC回收,是G1控制停顿时间的核心优化区域。
- 回收逻辑:老年代不会单独触发回收,而是在 Mixed GC(混合回收)中与新生代 Region 一起被回收。
老年代Region总占比达到阈值(默认45%,可通过 -XX:InitiatingHeapOccupancyPercent 调整),JVM启动并发标记周期;并发标记完成后,筛选回收收益最高的老年代Region(垃圾占比高、回收耗时短),与新生代Region组成回收集合(CSet),执行STW并行回收;将CSet中老年代Region的存活对象,复制到空闲Region中(紧凑排列),原老年代Region标记为空闲Region,完成回收与压缩一体化操作。若老年代内存耗尽或并发标记失败,会触发Full GC(全程STW)。
-
巨型区域(Humongous Region,H 区)
- 角色定位:H区是G1专为巨型对象设计的专属区域,核心解决巨型对象直接进入老年代导致碎片化的痛点。大小超过单个Region容量50%的对象,需单独存储以避免跨Region引用复杂度。
- 分配规则:优先尝试分配单个H区,若对象大小超过单个H区容量,则分配连续的多个H区;分配时若无法找到足够连续的H区,JVM会先尝试回收空闲Region/低收益Region释放空间;若回收后仍无连续H区,触发Full GC(标记-整理内存,腾出连续空间)。
H区一旦分配,仅承载单个巨型对象,对象被回收后,所有关联的连续H区统一标记为空闲Region,可重新分配为其他角色。 - 回收机制:H区的回收与老年代Region回收逻辑一致。
Young GC仅针对新生代Region,H区不属于新生代集合,故不被扫描回收;并发标记周期会同步标记H区的存活状态,Mixed GC时随老年代Region一同被纳入CSet,存活对象复制到空闲Region,原H区变为空闲;触发Full GC时,H区随全堆一同被标记-整理,巨型对象若死亡则释放连续空间。

G1垃圾回收器并发标记流程图如下:

G1垃圾回收器并发标记周期
一、初始标记(Initial Mark,STW)
本阶段的核心任务是标记根节点直接可达的存活对象。
关键特性与关联逻辑:
- 必触发全局停顿(STW) ,但停顿时长极短,仅需遍历根集合并标记直接可达对象,无复杂遍历操作。
- 强制与新生代GC(Minor GC)绑定执行:G1会将初始标记嵌入一次Minor GC中,借助Minor GC对新生代的清理,为后续根区域扫描提供"干净"的Survivor区(无待回收的临时对象),避免重复扫描开销。
- 输出结果:完成根直接可达对象的标记,同时确定Survivor区中需作为后续扫描起点的存活对象,为根区域扫描阶段铺垫。
二、根区域扫描(Root Region Scanning)
本阶段针对初始标记后Survivor区中的存活对象,扫描其直接可达的老年代Region,并标记这些跨代引用指向的老年代对象,构建完整的根可达链延伸。
关键特性与约束:
- 并发执行:与应用程序线程并行运行,无停顿,不影响业务响应延迟。
- 互斥约束:严禁与Minor GC同时执行。原因是根区域扫描的起点是Survivor区对象,而Minor GC会移动、回收Survivor区对象,或修改其引用关系,导致扫描起点失效、标记结果错误。G1会通过锁机制阻塞Minor GC,直至根区域扫描完成。
- 核心作用:弥补初始标记仅覆盖根直接可达对象的不足,将标记范围从Survivor区延伸至老年代,为后续全堆并发标记奠定基础。
三、并发标记(Concurrent Marking)
本阶段基于前两阶段的标记结果,遍历整个堆的所有Region,递归扫描并标记所有存活对象,本质是完善全堆存活对象的标记图谱。
关键特性与机制:
- 并发执行:与应用程序线程并行,无主动停顿,但过程中可能被 Young GC 打断(非阻塞,仅暂停并发标记,优先执行 Young GC )。
- 中断与恢复:被 Young GC 打断后,待 Young GC 完成,并发标记会从断点处继续执行,无需从头重扫,保证效率。
- 增量更新机制:为应对并发标记期间应用程序修改引用关系的问题(如将未标记对象的引用赋值给已标记对象),G1采用增量更新(Incremental Update)机制,跟踪此类引用变更,确保被修改的引用指向的对象能被后续阶段重新处理,避免漏标。
- 与CMS的差异:CMS并发标记遍历整个堆,而G1基于Region遍历,后续可精准定位高垃圾占比Region,为混合回收提供依据。
四、重新标记(Remark,STW)
本阶段为纠正并发标记期间因应用程序运行导致的标记偏差,对标记结果进行最终修正,确保存活对象标记的准确性。
关键特性与操作:
- 全局停顿(STW):停顿时长短于CMS的重新标记(因G1有Region化管理和增量更新铺垫),长于初始标记。
- 修正内容:处理并发标记期间未完成的增量更新记录、标记新创建的对象、清理已失效的引用(如对象被回收后残留的引用)、处理弱引用、软引用等特殊引用类型(按规则标记或回收)。
- 优化机制:G1通过 SATB(Snapshot-At-The-Beginning)的辅助逻辑,缩小重新标记的扫描范围,减少停顿耗时。
五、独占清理(Exclusive Cleanup,STW)
本阶段为后续混合回收做准备,通过 STW 执行统计、排序和状态更新操作,不直接回收对象(仅规划回收范围)。
关键操作与作用:
- 全局停顿(STW):停顿时长较短,聚焦于数据统计与预处理。
- 核心操作:
- 统计各Region的存活对象数量、垃圾占比(回收收益 = 可回收空间 / 回收耗时),并按回收收益排序;
- 识别完全空闲 Region 和高价值老年代 Region,依据参数G1MixedGCLiveThresholdPercent(默认 65%),筛选出存活对象占比 ≤65% 的老年代 Region,标记为待混合回收区域;
- 更新记忆集(Remembered Set,RS):清理记忆集中的无效条目(如跨Region引用已失效),同步Region间的引用状态,确保后续回收时能精准识别跨Region引用,避免误回收。
- 输出结果:生成待混合回收的Region列表及优先级,为混合回收阶段提供核心依据。
六、并发清理(Concurrent Cleanup)
本阶段针对独占清理标记的完全空闲Region进行回收,释放内存空间,不影响应用程序运行。
关键特性与操作:
- 并发执行:与应用程序线程并行,无停顿,仅回收完全无存活对象的Region。
- 核心操作:将完全空闲的Region从已使用状态转为空闲,并归还给堆的空闲Region列表,供后续对象分配使用。
- 局限性:不处理部分空闲的Region(此类Region留至混合回收阶段,结合复制算法回收存活对象并释放空间),仅清理完全空闲Region可最大化并发效率,平衡延迟与吞吐量。
G1垃圾回收器并发标记周期的核心价值与协同逻辑
并发标记周期并非独立GC动作,而是针对老年代及巨型区域的存活对象普查流程,触发条件为老年代Region占比达到阈值(默认45%,可通过 -XX:InitiatingHeapOccupancyPercent 调整)。周期结束后,G1会生成Region回收优先级列表,为后续 Mixed GC 筛选回收集合(CSet)提供依据,同时确保回收时仅处理存活对象,提升压缩效率。
并发标记周期并非独立的内存回收阶段,而是连接 Young GC 与 Mixed GC 的关键枢纽,核心作用是在规避长时 STW(全停顿)的前提下,完成全堆分析与回收规划,为后续 Mixed GC 提供精准依据。其通过 SATB(Snapshot-At-The-Beginning,快照式标记)算法,以短时STW与并发执行的组合模式运行,兼顾标记准确性与低延迟。
该周期与 Young GC 呈现并行穿插、关键互斥的协同关系,既保证Young GC的及时性,又为老年代增量回收铺垫;与 Mixed GC 则是严格的前置决策-后置执行关系,是Mixed GC启动的必要前提。
G1 垃圾回收器的 Mixed GC
Mixed GC(混合回收)是 G1 实现低延迟与内存自整理的核心执行阶段,并非独立流程,而是并发标记周期完成后替代Young GC执行的混合回收模式,核心是在一次短时STW中同时处理新生代与老年代高价值Region,实现老年代增量式清理。
Mixed GC 触发的前置条件与触发信号:
并发标记周期完成是启动 Mixed GC 的必要前提,只有当并发标记完成全堆存活对象标记、生成老年代高价值 Region 列表后,G1 才会停止执行单纯的 Young GC;而直接触发信号仍与 Young GC 一致,即 Eden Region 完全耗尽,无法分配新对象。
Mixed GC 回收范围:
一是全部的新生代 Region,包含 Eden Region 与 Survivor Region;二是老年代中的高价值 Region,即并发标记周期筛选出的垃圾占比高、回收耗时短的 Region,这部分是根据停顿预测模型动态选择的;三是部分 Humongous Region(巨型对象区),若其垃圾占比达标,会随老年代高价值 Region 一同被回收。
Mixed GC的执行流程围绕可控停顿与内存压缩展开:
首先基于并发标记生成的优先级列表,结合 -XX:MaxGCPauseMillis(目标最大停顿时间)预设目标,通过停顿预测模型确定回收集(CSet),优先纳入高价值老年代Region与全部新生代 Region,确保单次STW时长控制在阈值内,这也是 Mixed GC 区别于传统全量老年代回收的关键;随后多GC线程并行扫描 CSet 内所有 Region 的存活对象,将存活对象复制至新空闲 Region------新生代存活对象复制至新 Survivor 或老年代 Region,老年代存活对象复制至新老年代Region,复制过程同步完成内存压缩,从根源解决CMS收集器 标记-清除 导致的碎片化问题;最后释放旧 Region 空间,更新记忆集与 Region 角色状态,恢复应用线程执行,全程STW时长通常为毫秒级。
分批次执行是 Mixed GC 的关键特性:
G1默认分8轮(可通过 -XX:G1MixedGCCountTarget 调整)逐步清理老年代高价值Region,而非一次性回收全量老年代。当老年代 Region平均存活占比超过 -XX:G1MixedGCLiveThresholdPercent(默认 65%)即回收收益过低或已完成预设轮次,Mixed GC 终止,G1 恢复为单纯 Young GC,直至下一次堆内存占用率达 InitiatingHeapOccupancyPercent 阈值,启动新并发标记周期,再进入下一轮 Mixed GC 循环,形成回收闭环。
Mixed GC 具备两个显著优势:
一是无额外碎片整理开销,其复制算法在回收时同步完成内存压缩,无需像 CMS 那样在多次回收后单独执行碎片整理;二是与应用线程的低冲突性,仅在回收阶段触发短时 STW,其余时间不阻塞应用运行,完美契合 G1 面向服务端应用的低延迟设计目标。
Full GC:异常兜底回收机制
G1 通过 Young GC、并发标记、Mixed GC 的分层协同,已最大限度规避长时STW,但在并发回收过程中,若出现内存分配或晋升速度远超回收速度的极端场景,仍会触发Full GC。需明确的是,G1的Full GC属于异常兜底机制,并非设计目标,应通过调优尽量规避。
Full GC 触发场景:
一是晋升失败,Mixed GC 或 Young GC 中存活对象需晋升老年代,但老年代无可用 Region,且并发标记未完成无法通过 Mixed GC 释放空间;二是 Humongous 对象分配失败,巨型对象无法找到足够连续的 Humongous Region 存储;三是并发标记滞后,堆内存占用率达标触发的并发标记周期尚未完成,而堆内存已耗尽无法分配新对象;四是显式触发,如应用调用 System.gc() (未禁用 -XX:+DisableExplicitGC)。
G1的 Full GC 默认采用多线程 STW 的 标记-整理 算法,相比 CMS 单线程 Full GC 停顿时间更短,但仍远长于 Mixed GC。调优核心思路为规避其触发:下调 InitiatingHeapOccupancyPercent 阈值让并发标记更早启动;优化巨型对象(增大 G1HeapRegionSize 或减少短期巨型对象创建);JDK12+启用 -XX:+G1UseAdaptiveIHOP 自适应调整并发标记时机;必要时适当调大堆内存,缓解内存压力。
G1 垃圾回收器核心参数设置
一、核心基础参数
- -XX:+UseG1GC
- 核心作用:显式启用 G1 垃圾回收器
- 技术细节:
JDK 8 需手动配置该参数才能启用 G1;JDK 9 及以上版本中,G1 已取代 CMS 成为默认垃圾回收器,无需显式指定;
启用后,JVM 会自动禁用 CMS、Parallel Old 等其他回收器,适配 Region 化内存管理逻辑。
- -XX:MaxGCPauseMillis=ms
- 核心作用:设置 G1 的目标最大停顿时间,单位为毫秒
- 技术细节:
默认值为 200ms,G1 会通过动态调整策略尝试达成该目标;
设置过严会导致 Young GC 频率增加吞吐量下降,同时 Mixed GC 单次回收的 Region 数量减少,老年代清理不及时增加 Full GC 风险。
- -XX:ParallelGCThreads=n
- 核心作用:设置 STW 阶段(如 Young GC、Mixed GC、Full GC)的并行 GC 工作线程数量
- 技术细节:
默认值为 CPU 核心数的 80%,最大不超过 CPU 核心数;
线程数过多会导致 CPU 上下文切换开销增大,反而降低回收效率;线程数过少会延长 STW 时长,大堆场景可适当上调。
- -XX:InitiatingHeapOccupancyPercent=percent
- 核心作用:设置触发并发标记周期的堆内存占用阈值
- 技术细节:
默认值为 45%,即当堆总占用率达到 45% 时,启动并发标记周期;
该值一旦手动设置,G1 不会为了适配 MaxGCPauseMillis 而调整;
阈值偏大并发标记启动过晚,易触发 Full GC;阈值偏小并发标记过于频繁,应用吞吐量下降;
JDK 12 + 新增自适应参数 -XX:+G1UseAdaptiveIHOP(默认启用),JVM 会根据应用内存行为动态调整 IHOP 阈值,替代固定值;
二、关键辅助参数
- -XX:G1HeapRegionSize=n
- 核心作用:指定单个 Region 的大小,是 G1 内存布局的基础参数
- 技术细节:
默认值为堆总大小 ÷ 2048(自动修正为范围内的 2 的幂);
取值范围为 1MB~32MB,且必须是 2 的整数次幂,设置值需平衡 Region 数量与管理开销;
- -XX:G1MixedGCCountTarget=n
- 核心作用:设置并发标记完成后,Mixed GC 的默认执行轮次
- 技术细节:
默认值为 8 轮,G1 会分 8 轮逐步回收老年代高价值 Region;
延迟敏感场景可适当减少轮次,每轮回收更少 Region,控制单次停顿;吞吐量敏感场景可适当增加轮次,加快老年代清理。
- -XX:G1MixedGCLiveThresholdPercent=percent
- 核心作用:设置 Mixed GC 回收老年代 Region 的存活对象占比阈值
- 技术细节:
默认值为 65%,即仅当老年代 Region 的存活对象占比≤65%时,才会被选为高价值 Region 纳入 Mixed GC;
阈值越低,Mixed GC 回收的 Region 越少,老年代清理越慢;阈值越高,单次 Mixed GC 停顿越长。