经典垃圾回收器和现代垃圾回收器
CMS 及以后的垃圾回收器被称为“现代垃圾回收器”,CMS 之前的通常称为“传统垃圾回收器”或“经典垃圾回收器”。
为什么这样区分?
1. 并发与低停顿
- 传统垃圾回收器(如 Serial GC、Parallel GC)在回收时会长时间 Stop-The-World(STW),所有应用线程暂停,影响响应速度。
- 现代垃圾回收器(从 CMS 开始,如 CMS、G1、ZGC、Shenandoah)引入了并发标记、并发清理等机制,GC 线程和应用线程可以同时工作,大大降低了停顿时间,提升了应用的响应性。
2. 适应多核和大堆
- 现代 GC 更好地支持多核 CPU 和大内存堆,能在大规模服务端场景下保持良好性能。
3. 更智能的算法
- 现代 GC 引入了三色标记法、SATB、分区回收、预测停顿等更智能的算法和机制,能更好地平衡吞吐量与延迟。
总结
- CMS 之前:Serial GC、Parallel GC,称为“传统”或“经典”垃圾回收器,特点是全停顿、简单高效但不适合低延迟场景。
- CMS 及以后:CMS、G1、ZGC、Shenandoah,称为“现代垃圾回收器”,特点是并发、低停顿、适合大堆和多核环境。
本质区别:
现代垃圾回收器的目标是降低应用停顿时间,提高响应性和可扩展性,而传统垃圾回收器更关注实现简单和吞吐量。
Serial GC(串行回收器)
Serial GC(串行回收器)是 Java 虚拟机中最简单的一种垃圾回收器。它的特点是所有垃圾回收操作都由单线程完成,在回收期间会暂停所有应用线程(Stop-The-World),直到回收结束。
一、适用场景
- 适合单核 CPU、小内存、对延迟要求不高的应用(如嵌入式、客户端、测试环境)。
- 在服务器端或多核环境下不推荐使用。
二、运行机制和原理
1. 新生代回收(Minor GC)
-
采用复制算法(Copying) :
- 新生代(Young Generation)分为 Eden 区和两个 Survivor 区(S0、S1)。
- 大部分对象在 Eden 区分配。
- 当 Eden 区满时,触发 Minor GC。
- 存活对象从 Eden 和 From Survivor 区复制到 To Survivor 区,年龄增加,达到阈值晋升到老年代。
- 整个过程只有一个 GC 线程,所有应用线程暂停。
2. 老年代回收(Major/Full GC)
-
采用标记-整理算法(Mark-Compact) :
- 首先标记所有存活对象。
- 然后将存活对象向一端移动,清理无效空间,避免内存碎片。
- 依然是单线程,所有应用线程暂停。
3. Stop-The-World
- 无论 Minor GC 还是 Major/Full GC,Serial GC 都会暂停所有应用线程,直到回收完成。
三、优缺点
优点
- 实现简单,稳定可靠。
- 在单核、小堆环境下,GC 开销低,性能反而不错。
缺点
- 单线程,无法利用多核 CPU。
- GC 停顿时间长,应用不可用,影响用户体验。
- 不适合高并发、大内存、对响应时间敏感的场景。
四、如何启用
- 显式指定参数:
-XX:+UseSerialGC
- 在单核或小内存环境下,JVM 可能自动选择 Serial GC。
五、总结
- Serial GC 是最基础的垃圾回收器,所有回收工作由单线程完成,回收期间应用完全暂停。
- 适合资源有限、对延迟不敏感的场景。
- 现代服务器端应用一般不推荐使用。
六、日志分析
下面是一个典型的 Serial GC(串行回收器)GC 日志样例(加上 -XX:+PrintGCDetails -XX:+PrintGCDateStamps
参数):
2024-06-10T15:30:12.123+0800: 0.123: [GC (Allocation Failure)
[DefNew: 6144K->512K(6144K), 0.0056789 secs]
6144K->1536K(19840K), 0.0057890 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
2024-06-10T15:30:13.456+0800: 1.456: [Full GC (Ergonomics)
[Tenured: 1024K->512K(13696K), 0.0101234 secs]
2560K->512K(19840K), [Metaspace: 3500K->3500K(1056768K)], 0.0102345 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
日志说明
[GC (Allocation Failure) ...]
:Minor GC(新生代回收),原因是分配失败。[DefNew: 6144K->512K(6144K), ...]
:新生代(DefNew)从 6144K 回收到 512K,总容量 6144K。6144K->1536K(19840K)
:整个堆从 6144K 回收到 1536K,总容量 19840K。[Full GC (Ergonomics) ...]
:Full GC(包括老年代),原因是 JVM Ergonomics(自适应调整)。[Tenured: 1024K->512K(13696K), ...]
:老年代(Tenured)从 1024K 回收到 512K,总容量 13696K。[Metaspace: 3500K->3500K(1056768K)]
:元空间使用情况。user/sys/real
:GC 期间的用户态、内核态和实际耗时。
下面对 Serial GC 日志样例的每一部分做详细解释:
日志样例
2024-06-10T15:30:12.123+0800: 0.123: [GC (Allocation Failure)
[DefNew: 6144K->512K(6144K), 0.0056789 secs]
6144K->1536K(19840K), 0.0057890 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
1. 2024-06-10T15:30:12.123+0800: 0.123:
2024-06-10T15:30:12.123+0800
:GC 发生的日期和时间。0.123:
:JVM 启动后的时间(秒),即本次 GC 发生在 JVM 启动后 0.123 秒。
2. [GC (Allocation Failure)
[GC
:表示一次 Minor GC(新生代回收)。(Allocation Failure)
:GC 触发原因,这里是因为新生代空间不足,无法分配新对象。
3. [DefNew: 6144K->512K(6144K), 0.0056789 secs]
DefNew
:新生代(Default New Generation)。6144K->512K(6144K)
:GC 前新生代使用 6144K,GC 后剩 512K,总容量 6144K。0.0056789 secs
:新生代回收耗时约 5.7 毫秒。
4. 6144K->1536K(19840K), 0.0057890 secs]
6144K->1536K(19840K)
:GC 前整个堆使用 6144K,GC 后剩 1536K,总容量 19840K。0.0057890 secs
:整个 GC 过程耗时约 5.8 毫秒。
5. [Times: user=0.01 sys=0.00, real=0.01 secs]
user=0.01
:用户态 CPU 时间(0.01 秒)。sys=0.00
:内核态 CPU 时间(0.00 秒)。real=0.01 secs
:GC 实际耗时(0.01 秒)。
Full GC 日志示例
2024-06-10T15:30:13.456+0800: 1.456: [Full GC (Ergonomics)
[Tenured: 1024K->512K(13696K), 0.0101234 secs]
2560K->512K(19840K), [Metaspace: 3500K->3500K(1056768K)], 0.0102345 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
1. [Full GC (Ergonomics)
Full GC
:表示一次 Full GC(包括老年代和新生代)。(Ergonomics)
:GC 触发原因,这里是 JVM 自适应调整。
2. [Tenured: 1024K->512K(13696K), 0.0101234 secs]
Tenured
:老年代。1024K->512K(13696K)
:GC 前老年代使用 1024K,GC 后剩 512K,总容量 13696K。0.0101234 secs
:老年代回收耗时约 10 毫秒。
3. 2560K->512K(19840K)
2560K->512K(19840K)
:GC 前整个堆使用 2560K,GC 后剩 512K,总容量 19840K。
4. [Metaspace: 3500K->3500K(1056768K)]
Metaspace
:元空间(存放类元数据)。3500K->3500K(1056768K)
:GC 前后元空间使用 3500K,总容量 1056768K(未回收)。
5. 0.0102345 secs
- 整个 Full GC 过程耗时约 10 毫秒。
Parallel GC(并行回收器/吞吐量优先 GC)
一、什么是 Parallel GC?
Parallel GC(也叫吞吐量优先 GC、Throughput Collector)是一种多线程并行执行垃圾回收任务的回收器。
它的目标是最大化应用的吞吐量,即让应用程序花更多时间做“正事”,而不是做 GC。
- JDK8 默认的垃圾回收器就是 Parallel GC。
- 主要参数:
-XX:+UseParallelGC
(新生代),-XX:+UseParallelOldGC
(老年代)。
二、运行机制和原理
1. 新生代回收(Minor GC)
- 采用复制算法(Copying) ,与 Serial GC 类似,但回收过程由多个 GC 线程并行完成。
- 新生代分为 Eden 区和两个 Survivor 区(S0、S1)。
- 当 Eden 区满时,触发 Minor GC。
- 存活对象从 Eden 和 From Survivor 区复制到 To Survivor 区,年龄增加,达到阈值晋升到老年代。
- 多个 GC 线程同时工作,大大加快回收速度。
2. 老年代回收(Major/Full GC)
- 采用多线程标记-整理算法(Parallel Mark-Compact) 。
- 标记所有存活对象,然后将其整理到一端,清理无效空间,避免内存碎片。
- 老年代的回收同样是多线程并行完成(需开启
-XX:+UseParallelOldGC
,JDK8 默认开启)。
3. Stop-The-World
- 无论 Minor GC 还是 Major/Full GC,Parallel GC 都会暂停所有应用线程,直到回收完成。
- 但由于回收过程是多线程并行,GC 停顿时间比 Serial GC 短很多,尤其在多核 CPU 上优势明显。
三、优缺点
优点
- 高吞吐量:GC 线程并行,回收速度快,应用可用时间比例高。
- 适合多核服务器:能充分利用多核 CPU 的计算能力。
- 实现稳定,适合大多数通用场景。
缺点
- GC 停顿依然存在:所有回收过程都 Stop-The-World,不适合对延迟极敏感的应用。
- 不支持并发回收:应用线程和 GC 线程不能同时运行。
四、常用参数
-XX:+UseParallelGC
:启用并行新生代回收(默认 JDK8)。-XX:+UseParallelOldGC
:启用并行老年代回收(JDK8 默认)。-XX:ParallelGCThreads=N
:设置 GC 线程数(默认与 CPU 核心数相关)。-XX:MaxGCPauseMillis=xxx
:期望最大 GC 停顿时间(JVM 尽量满足,但不保证)。
五、适用场景
- 多核服务器、对吞吐量要求高、对单次停顿时间要求不高的应用。
- 批处理、数据分析、后台服务等。
六、总结
- Parallel GC 是多线程并行执行的 Stop-The-World 回收器,追求高吞吐量。
- 新生代和老年代都能并行回收,适合多核、高吞吐场景。
- 不适合对延迟极敏感的应用(如金融、实时系统),这些场景建议用 G1、ZGC、Shenandoah 等低停顿 GC。
七、日志分析
下面是一个典型的 Parallel GC(吞吐量优先 GC)日志样例(加上 -XX:+PrintGCDetails -XX:+PrintGCDateStamps
参数):
2024-06-10T16:00:12.123+0800: 0.123: [GC (Allocation Failure)
[PSYoungGen: 6144K->512K(9216K)]
8192K->2048K(19456K), 0.0045678 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
2024-06-10T16:01:15.456+0800: 63.456: [Full GC (Ergonomics)
[PSYoungGen: 1024K->0K(9216K)]
[ParOldGen: 8192K->1024K(10240K)]
9216K->1024K(19456K), [Metaspace: 3500K->3500K(1056768K)], 0.0123456 secs]
[Times: user=0.03 sys=0.00, real=0.01 secs]
日志说明
[GC (Allocation Failure) ...]
:Minor GC(新生代回收),因分配失败触发。[PSYoungGen: 6144K->512K(9216K)]
:新生代(Parallel Scavenge Young Generation)GC 前后使用情况。[ParOldGen: 8192K->1024K(10240K)]
:老年代(Parallel Old Generation)GC 前后使用情况。[Full GC (Ergonomics) ...]
:Full GC(包括新生代和老年代),因 JVM 自适应调整触发。[Metaspace: 3500K->3500K(1056768K)]
:元空间使用情况。user/sys/real
:GC 期间的用户态、内核态和实际耗时。
下面详细解读上面 Parallel GC 日志样例的每一部分含义:
日志样例
2024-06-10T16:00:12.123+0800: 0.123: [GC (Allocation Failure)
[PSYoungGen: 6144K->512K(9216K)]
8192K->2048K(19456K), 0.0045678 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
2024-06-10T16:01:15.456+0800: 63.456: [Full GC (Ergonomics)
[PSYoungGen: 1024K->0K(9216K)]
[ParOldGen: 8192K->1024K(10240K)]
9216K->1024K(19456K), [Metaspace: 3500K->3500K(1056768K)], 0.0123456 secs]
[Times: user=0.03 sys=0.00, real=0.01 secs]
第一条日志(Minor GC)
2024-06-10T16:00:12.123+0800: 0.123: [GC (Allocation Failure)
[PSYoungGen: 6144K->512K(9216K)]
8192K->2048K(19456K), 0.0045678 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
-
2024-06-10T16:00:12.123+0800
:GC 发生的日期和时间。 -
0.123:
:JVM 启动后 0.123 秒发生 GC。 -
[GC (Allocation Failure)
:Minor GC,因新生代空间分配失败触发。 -
[PSYoungGen: 6144K->512K(9216K)]
:- 新生代(Parallel Scavenge Young Generation)GC 前使用 6144K,GC 后剩 512K,总容量 9216K。
-
8192K->2048K(19456K)
:- 整个堆(新生代+老年代)GC 前使用 8192K,GC 后剩 2048K,总容量 19456K。
-
0.0045678 secs
:本次 GC 总耗时约 4.6 毫秒。 -
[Times: user=0.01 sys=0.00, real=0.01 secs]
:user=0.01
:用户态 CPU 时间 0.01 秒。sys=0.00
:内核态 CPU 时间 0.00 秒。real=0.01 secs
:GC 实际耗时 0.01 秒。
第二条日志(Full GC)
2024-06-10T16:01:15.456+0800: 63.456: [Full GC (Ergonomics)
[PSYoungGen: 1024K->0K(9216K)]
[ParOldGen: 8192K->1024K(10240K)]
9216K->1024K(19456K), [Metaspace: 3500K->3500K(1056768K)], 0.0123456 secs]
[Times: user=0.03 sys=0.00, real=0.01 secs]
-
2024-06-10T16:01:15.456+0800
:GC 发生的日期和时间。 -
63.456:
:JVM 启动后 63.456 秒发生 GC。 -
[Full GC (Ergonomics)
:Full GC,因 JVM 自适应调整触发。 -
[PSYoungGen: 1024K->0K(9216K)]
:- 新生代 GC 前使用 1024K,GC 后为 0K,总容量 9216K。
-
[ParOldGen: 8192K->1024K(10240K)]
:- 老年代(Parallel Old Generation)GC 前使用 8192K,GC 后为 1024K,总容量 10240K。
-
9216K->1024K(19456K)
:- 整个堆 GC 前使用 9216K,GC 后为 1024K,总容量 19456K。
-
[Metaspace: 3500K->3500K(1056768K)]
:- 元空间 GC 前后都为 3500K,总容量 1056768K(未回收)。
-
0.0123456 secs
:本次 Full GC 总耗时约 12.3 毫秒。 -
[Times: user=0.03 sys=0.00, real=0.01 secs]
:user=0.03
:用户态 CPU 时间 0.03 秒。sys=0.00
:内核态 CPU 时间 0.00 秒。real=0.01 secs
:GC 实际耗时 0.01 秒。
总结
- PSYoungGen:新生代(Parallel Scavenge)。
- ParOldGen:老年代(Parallel Old)。
- Metaspace:元空间(类元数据)。
- user/sys/real:分别表示 GC 期间的用户态、内核态和实际耗时。
- GC/Full GC:分别表示 Minor GC 和 Full GC。