垃圾收集器
垃圾回收集分类
串行收集器
并行收集器
CMS收集器
G1收集器
新生代串行收集器
老年代串行收集器
新生代收集器
主要用于回收新生代(Young Generation),通常采用复制算法。
Serial 收集器
最基础的新生代收集器,负责回收 Eden 和 Survivor 区。
使用场景
- 客户端模式:内存较小(几百 MB),单核 CPU 环境。
- 简单应用:不需要复杂线程交互,追求单线程极致效率的场景。
原理
只使用一个 GC 线程,回收时必须暂停所有用户线程。
工作流程
- 新生代空间满,触发 Minor GC。
- STW 开始,暂停用户线程。
- 单线程将存活对象从 Eden 和 From Survivor 复制到 To Survivor。
- 清空 Eden 和 From Survivor。
- STW 结束,恢复用户线程。
优缺点
- 优点:实现简单,单线程效率极高(无线程切换开销),占用内存少。
- 缺点:必须 STW,停顿时间长,无法利用多核 CPU。
ParNew 收集器
Serial 收集器的多线程版本。
使用场景
- 多核 CPU 服务器。
- 配合 CMS 使用:它是许多老版本 JDK 中 CMS 收集器在新生代的默认搭档。
原理
- 多线程并行:开启多个 GC 线程同时进行复制回收,缩短 STW 时间。
- 算法:复制算法。
工作流程
- 触发 GC。
- STW 开始。
- 多个 GC 线程并行扫描 Eden 和 Survivor 区,将存活对象复制到新的 Survivor 区。
- 所有线程同步完成后,STW 结束
优缺点
- 优点:相比 Serial,在多核环境下 STW 时间更短,效率更高。
- 缺点:在单核环境下,由于线程同步开销,性能甚至不如 Serial;必须配合 CMS 或其他老年代收集器使用。
Parallel Scavenge 收集器
专注于吞吐量的新生代收集器。
使用场景
- 后台运算、批处理、大数据计算:关注整体任务完成时间,对单次响应延迟不敏感。
- JDK8默认搭档:通常与 Parallel Old 搭配使用。
原理
- 吞吐量优先:目标是最大化 CPU 用于运行用户代码的时间(吞吐量 = 运行用户代码时间 / (运行用户代码时间 + GC 时间))。
- 自适应调节:可以根据历史数据自动调整 Eden 和 Survivor 区的比例,以达到设定的吞吐量目标。
工作流程
- 触发 GC。
- STW 开始。
- 多线程并行回收,重点在于快速完成回收任务,保证 CPU 利用率。
- STW 结束。
优缺点
- 优点:高吞吐量,适合计算密集型任务。
- 缺点:可控性较差(早期版本),停顿时间不可预测,不适合交互式应用。
老年代收集器
用于回收老年代(Old Generation),通常采用标记-整理 或标记-清除算法。
Serial Old收集器
Serial 收集器的老年代版本。
使用场景
- JDK1.5之前:与 Serial 搭配使用。
- CMS的备选:当 CMS 发生"并发模式失败"时,JVM 会临时启用 Serial Old 进行 Full GC(这是灾难性的停顿)。
原理
单线程执行,标记-整理算法。
工作流程
用户线程1
用户线程2
用户线程3
用户线程4
GC线程 STW
用户线程1
用户线程2
用户线程3
用户线程4
CPU0
Safepoint
CPU1
CPU2
CPU3
GC结束
结束
- 老年代空间不足,触发 Full GC。
- STW 开始。
- 单线程标记所有存活对象,然后将其向内存一端移动整理。
- 清理边界外内存。
- STW 结束。
优缺点
- 优点:简单、稳定。
- 缺点:速度极慢,停顿时间非常长。
Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。JDK1.6开始提供。
使用场景
- 高吞吐量场景:与 Parallel Scavenge 搭配,组成"吞吐量优先"组合。
- 多核 CPU 服务器。
原理
使用多个线程进行标记和整理,标记-整理算法。
工作流程
用户线程1
用户线程2
用户线程3
用户线程4
GC线程1
GC线程2
GC线程3
GC线程4
用户线程1
用户线程2
用户线程3
用户线程4
GC线程1
GC线程2
GC线程3
GC线程4
CPU0
Safepoint1
CPU1
CPU2
CPU3
新生代GC结束
Safepoint2
老年代GC结束
- 触发 Full GC。
- STW 开始。
- 多线程并行标记存活对象。
- 多线程并行整理(移动)存活对象。
- STW 结束。
优缺点
- 优点:高吞吐量,充分利用多核 CPU。
- 缺点:依然会 STW,停顿时间不可控。
CMS (Concurrent Mark Sweep) 收集器
CMS(concurrent mark sweep)是以获取最短垃圾收集停顿时间为目标的收集器。
使用场景
- Web应用、B/S系统:对响应时间敏感,希望页面加载快。
- 注意:JDK 9 废弃,JDK 14 移除。
原理
大部分时间与用户线程同时运行,标记-清除算法。
工作流程
用户线程1
用户线程2
用户线程3
用户线程4
初始标记
用户线程1
用户线程2
并发标记
用户线程4
重新标记
重新标记
重新标记
重新标记
用户线程1
用户线程2
并发清理
用户线程4
用户线程1
用户线程2
重置线程
用户线程4
CPU0
Safepoint1
CPU1
CPU2
CPU3
Safepoint2
Safepoint3
Safepoint4
Safepoint5
结束
- 初始标记 (STW):标记 GC Roots 直接关联的对象(很快)。
- 并发标记 :遍历整个老年代对象图,标记存活对象(用户线程运行)。
- 重新标记 (STW):修正并发期间变动的对象标记。
- 并发清除 :清理死亡对象(用户线程运行)。
优缺点
-
优点:低停顿,并发收集,用户体验好。
-
缺点:
- 内存碎片:标记-清除导致碎片,大对象分配困难,易触发 Full GC。
- 浮动垃圾:并发清除时产生的新垃圾无法当次回收。
- CPU 敏感:并发阶段占用 CPU,导致吞吐量下降。
整体收集器
G1 (Garbage-First) 收集器
面向服务端,在大内存环境下实现高吞吐量与低停顿的平衡。要围绕其独特的 Region 内存划分 和 可预测的停顿时间 两个核心概念展开
G1内存模型
G1将整个Java堆划分为多个大小相等的独立区域(Region),大小范围通常为1MB到32MB。
逻辑分代,物理不分代:虽然逻辑上仍然遵循"新生代"和"老年代"的概念,但在物理上,这些Region是混合分布的,没有固定的边界。
动态角色:每个Region在运行时可以动态地扮演不同角色:
- Eden Region:存放新创建的对象。
- Survivor Region:存放Young GC后存活的对象。
- Old Region:存放长期存活的对象。
- Humongous Region:专门用于存储大对象(大小超过单个Region的50%)。
这种设计使得G1可以灵活地调整各代内存的比例,并实现"垃圾优先"的回收策略。
使用场景
- 堆内存 > 6GB,多核 CPU。
- 希望停顿时间可控(如设定不超过 200ms)。
- JDK 9+ 默认收集器。
原理
- G1把内存划分为多个独立的区域。
- G1仍然保留分代思想,保留了年轻代和老年代,他们不再是物理隔离而是一部分Region的集合。
- G1能够充分利用多CPU、多核环境硬件优势,尽量缩短STW。
- G1整体采用标记整理算法,局部使用复制算法,不会产生内存碎片。
- G1的停顿可预测,可以指定某个时间段内用于垃圾回收的时间不会超过设置的值。
- G1跟踪每个Region中的垃圾价值大小,维护一个优先队列,每次根据允许的时间回收价值最大的区域,从而保证高效的垃圾回收。G1把内存划分为多个独立的区域。
- G1仍然保留分代思想,保留了年轻代和老年代,他们不再是物理隔离而是一部分Region的集合。
- G1能够充分利用多CPU、多核环境硬件优势,尽量缩短STW。
- G1整体采用标记整理算法,局部使用复制算法,不会产生内存碎片。
- G1的停顿可预测,可以指定某个时间段内用于垃圾回收的时间不会超过设置的值。
- G1跟踪每个Region中的垃圾价值大小,维护一个优先队列,每次根据允许的时间回收价值最大的区域,从而保证高效的垃圾回收。
执行流程
Young GC是G1中最频繁的回收活动,专门用于清理新生代(所有Eden和Survivor Region)。
- 触发条件:当Eden Region空间不足,无法为新对象分配内存时触发。
- 停顿特点 :这是一个 STW (Stop-The-World) 过程,即会暂停所有应用线程。但由于只回收新生代,停顿时间通常非常短暂。
用户线程1
用户线程2
用户线程3
用户线程4
GC线程1
GC线程2
GC线程3
GC线程4
CPU0
Safepoint1
CPU1
CPU2
CPU3
新生代GC结束
- 根扫描:扫描GC Roots,找出直接引用新生代的对象。
- 处理跨代引用:利用记忆集(Remembered Set, RSet)快速找出老年代对象引用新生代对象的情况,避免了扫描整个老年代,这是G1高效的关键。
- 标记存活对象:综合以上两步,标记出新生代中所有存活的对象。
- 复制存活对象:采用复制算法,将存活对象复制到新的Survivor Region。如果对象年龄达到阈值(默认15),则会晋升到Old Region。
- 清理:清空所有被回收的Eden和旧的Survivor Region,使其变为空闲Region,可供后续分配。
Mixed GC (混合回收)
Mixed GC是G1的核心和亮点,它不仅仅回收新生代,还会有选择地回收一部分老年代Region,从而有效避免Full GC的发生。
- 触发条件 :当老年代占用堆内存的比例达到一个阈值(由
-XX:InitiatingHeapOccupancyPercent参数控制,默认为45%)时,会启动一个并发标记周期,为Mixed GC做准备。 - 停顿特点 :同样是STW,但G1通过停顿预测模型 ,能够根据用户设定的目标停顿时间(
-XX:MaxGCPauseMillis,默认200ms),智能地选择本次回收的Region数量,从而将停顿时间控制在目标范围内。
用户线程1
用户线程2
用户线程3
用户线程4
初始标记
用户线程1
用户线程2
并发标记
用户线程4
最终标记
最终标记
最终标记
最终标记
筛选回收
筛选回收
筛选回收
筛选回收
CPU0
Safepoint1
CPU1
CPU2
CPU3
Safepoint2
Safepoint3
Safepoint4
Safepoint5
- 并发标记:在Mixed GC的STW暂停发生前,G1会先进行一次并发标记。这个过程与应用线程并行,遍历整个堆,标记出所有存活对象,并统计每个老年代Region的垃圾占比。
- 筛选回收集(CSet):根据"垃圾优先"原则,G1会从老年代中选出垃圾占比最高的一批Region,与所有新生代Region一起,构成一个"回收集"(Collection Set, CSet)。
- STW回收 :暂停应用线程,将CSet中所有Region的存活对象,通过复制算法移动到其他空闲Region中。
- 释放空间:回收完成后,CSet中的所有Region都被清空,立即可用。
Full GC (兜底回收)
Full GC是G1的"失败模式",是一种代价高昂的兜底机制,应极力避免。
-
触发条件:当G1的并发回收(Mixed GC)速度跟不上对象的分配速度,导致堆内存被耗尽时,就会触发Full GC。
-
停顿特点 :这是一个长时间的 STW 过程,会严重损害应用性能。
-
回收流程:
- G1会放弃其所有并发和增量优势,退化为一个类似 Serial Old 的单线程收集器(JDK 10之后已改进为并行)。
- 它会使用 标记-整理 算法,对整个堆(包括所有新生代和老年代)进行一次完整的扫描和整理。
优缺点
-
优点:
- 停顿可控:用户可指定期望停顿时间。
- 无内存碎片:避免了 Full GC。
-
缺点:内存占用高(维护 Region 信息),CPU 开销大(写屏障),小堆内存下优势不明显。
总结
| 收集器 | 适用代区 | 核心算法 | 线程模式 | 核心目标 | 主要缺点 |
|---|---|---|---|---|---|
| Serial | 新生代 | 复制 | 单线程 | 简单高效 | 必须 STW,单线程 |
| ParNew | 新生代 | 复制 | 多线程 | 配合 CMS | 单核下不如 Serial |
| Parallel Scavenge | 新生代 | 复制 | 多线程 | 高吞吐量 | 停顿不可控 |
| Serial Old | 老年代 | 标记-整理 | 单线程 | 简单稳定 | 速度极慢 |
| Parallel Old | 老年代 | 标记-整理 | 多线程 | 高吞吐量 | 停顿不可控 |
| CMS | 老年代 | 标记-清除 | 并发 | 低停顿 | 内存碎片,浮动垃圾 |
| G1 | 整个堆 | 标记-整理+复制 | 并发+并行 | 平衡+可控 | 内存/CPU 开销大 |
参数配置
1. 基础串行与并行收集器 (Serial / ParNew / Parallel)
这组收集器主要用于 JDK 8 及更早版本,或者对吞吐量有特定要求的场景。
| 收集器名称 | 核心参数 (JVM 配置) | 说明与建议 | 适用场景 |
|---|---|---|---|
| Serial (新生代) | -XX:+UseSerialGC |
启用 Serial 收集器。 这是最古老的收集器,单线程工作,GC 时会触发 STW (Stop-The-World)。 | 单核 CPU、Client 模式、堆内存较小 (<200MB) 的简单应用。 |
| Serial Old (老年代) | (通常自动搭配) | Serial 的老年代版本,使用"标记-整理"算法。通常由 -XX:+UseSerialGC 自动启用。 |
配合 Serial 使用。 |
| ParNew (新生代) | -XX:+UseParNewGC |
启用 ParNew 收集器。 Serial 的多线程版本。它是 CMS 的默认新生代搭档。 | 多核 CPU,且必须配合 CMS 使用(JDK 9 已废弃)。 |
| Parallel Scavenge (新生代) | -XX:+UseParallelGC |
启用 Parallel Scavenge。 关注吞吐量(CPU 运行代码的时间占比)。 | 后台计算、批处理任务,不需要低延迟,只求总运行时间最短。 |
| Parallel Old (老年代) | (自动搭配) | Parallel Scavenge 的老年代搭档。使用多线程"标记-整理"算法。 | 配合 Parallel Scavenge 使用。 |
| 通用调优参数 | -XX:MaxGCPauseMillis=N |
目标最大停顿时间(Parallel 收集器支持,但可能导致吞吐量下降)。 | 尝试平衡停顿与吞吐量。 |
-XX:GCTimeRatio=N |
设置 N=19 表示允许 GC 占 5% 时间。 |
CMS 收集器 (低延迟优先)
CMS (Concurrent Mark Sweep) 是 JDK 8 时代追求低延迟的主流选择,它通常与 ParNew 组合使用。
| 参数类别 | 参数名称 | 默认值 | 说明与建议 |
|---|---|---|---|
| 核心启用 | -XX:+UseConcMarkSweepGC |
- | 启用 CMS 收集器。 在 JDK 8 中,指定此参数后,新生代会自动使用 ParNew。 |
| 触发控制 | -XX:CMSInitiatingOccupancyFraction |
68% (JDK 6/7) 92% (JDK 8) | 老年代占用阈值。 当老年代使用率达到此百分比时,触发 CMS GC。建议设置为 75% 左右,预留缓冲空间防止并发模式失败。 |
-XX:+UseCMSInitiatingOccupancyOnly |
关闭 | 仅使用阈值触发。 如果不加此参数,JVM 会根据历史数据动态调整触发阈值;加上后则严格遵循上面的配置。 | |
| 线程控制 | -XX:ConcGCThreads |
并行线程的 1/4 | 并发阶段线程数。 用于并发标记和重新标记阶段,与应用线程并行运行。 |
-XX:ParallelGCThreads |
CPU 核心数 | STW 阶段并行线程数。 用于初始标记和最终标记阶段。 | |
| 碎片处理 | -XX:+UseCMSCompactAtFullCollection |
开启 | Full GC 时压缩。 CMS 会产生碎片,Full GC 时开启压缩整理可以消除碎片,但会增加 STW 时间。 |
-XX:CMSFullGCsBeforeCompaction |
0 | 压缩频率。 设置为 N 表示每 N 次 Full GC 进行一次压缩。 |
G1 收集器 (分区与可预测停顿)
G1 (Garbage-First) 是 JDK 9 的默认收集器,旨在平衡吞吐量和延迟,适合大堆内存
| 参数类别 | 参数名称 | 默认值 | 说明与建议 |
|---|---|---|---|
| 核心控制 | -XX:+UseG1GC |
- | 启用 G1 收集器。 |
-XX:MaxGCPauseMillis |
200ms | 目标最大停顿时间。 G1 会根据此目标动态调整年轻代大小和回收的老年代区域数量。 | |
| 内存分区 | -XX:G1HeapRegionSize |
自动 (1M-32M) | Region 大小。 必须是 2 的 N 次幂。G1 会根据堆大小自动计算,通常无需手动干预。 |
-XX:G1NewSizePercent |
5% | 年轻代最小占比。 G1 动态调整年轻代时的下限。 | |
-XX:G1MaxNewSizePercent |
60% | 年轻代最大占比。 G1 动态调整年轻代时的上限。 | |
| 触发与回收 | -XX:InitiatingHeapOccupancyPercent (IHOP) |
45% | 触发并发标记阈值。 当堆占用率达到此值时,启动并发标记周期。 |
-XX:G1MixedGCLiveThresholdPercent |
85% | 混合回收存活率阈值。 只有存活率低于此值的老年代 Region 才会被选入回收集。 | |
-XX:G1ReservePercent |
10% | 预留内存比例。 防止对象晋升失败导致 Full GC,建议适当调大(如 15%)。 | |
| 线程控制 | -XX:ParallelGCThreads |
CPU 核心数 | STW 阶段并行线程数。 |
-XX:ConcGCThreads |
并行线程的 1/4 | 并发阶段线程数。 |