JDK 8 默认 GC 收集器
在《深入理解 Java 虚拟机》第三版第 128 页中,提及在 JDK 9 之前,Server 模式下默认的垃圾收集器组合是Parallel Scavenge+Serial Old(即PS MarkSweep)。为了验证这一说法是否准确,本文将通过一系列实验进行探究。
1. 查看当前 JVM 默认参数
首先,我们直接使用命令查看当前 JVM 的默认参数配置:
java -XX:+PrintCommandLineFlags -version
执行上述命令,输出结果如下:
-XX:InitialHeapSize=268435456
-XX:MaxHeapSize=4294967296
-XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:+UseParallelGC
java version "1.8.0_162"
Java(TM) SE Runtime Environment (build 1.8.0_162-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.162-b12, mixed mode)
从输出结果的第六行可以看出,JVM 默认启用了-XX:+UseParallelGC。根据书籍或网络资料的描述,启用此参数后,新生代会使用Parallel Scavenge收集器,而老年代则默认使用Serial Old收集器,形成Parallel Scavenge + Serial Old的组合。
2. 通过 API 获取实际收集器名称
为了进一步验证,笔者决定通过 Java 代码来获取当前 JVM 实际使用的垃圾收集器。ManagementFactory.getGarbageCollectorMXBeans()方法可以返回当前 JVM 中的所有垃圾收集器 MBean。具体实现代码如下:
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.List;
public class GcCollectorPrinter {
public static void main(String[] args) {
List<GarbageCollectorMXBean> beans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean bean : beans) {
System.out.println(bean.getName());
}
}
}
运行上述代码,输出结果如下:
PS Scavenge
PS MarkSweep
此时产生疑问:PS MarkSweep是否等同于Serial Old?为了探究-XX:+UseParallelOldGC参数的影响,笔者配置该参数后再次运行程序,执行的命令如下:
javac GcCollectorPrinter.java
java -XX:+UseParallelOldGC GcCollectorPrinter
输出结果如下:
PS Scavenge
PS MarkSweep
令人困惑的是,无论是使用-XX:+UseParallelOldGC还是-XX:+UseParallelGC(默认情况),程序输出的收集器名称都显示为PS MarkSweep。这引发了一个核心问题:PS MarkSweep究竟是指Serial Old还是Parallel Old?
基于此,笔者提出了两个猜想:
PS MarkSweep可能是一个通用别名,既可以指代Serial Old,也可以指代Parallel Old,因为它们的底层实现可能存在相似之处。-XX:+UseParallelGC和-XX:+UseParallelOldGC最终都导致 JVM 使用Parallel Old收集器。
3. 通过 GC 日志确认老年代收集器
为了进一步明确,并鉴于PS MarkSweep可能只是一个通用别名,笔者决定通过打印详细的 GC 日志来观察实际使用的老年代收集器。-XX:+PrintGCDetails参数能够输出详细的 GC 分区信息。为此,笔者再次运行之前的两个示例,命令如下:
java -XX:+UseParallelOldGC -XX:+PrintGCDetails GcCollectorPrinter
java -XX:+PrintGCDetails GcCollectorPrinter
观察到令人一致的结果:
PS Scavenge
PS MarkSweep
Heap
PSYoungGen total 76288K, used 3932K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000)
eden space 65536K, 6% used [0x000000076ab00000,0x000000076aed7240,0x000000076eb00000)
from space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000)
to space 10752K, 0% used [0x000000076eb00000,0x000000076eb00000,0x000000076f580000)
ParOldGen total 175104K, used 0K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
object space 175104K, 0% used [0x00000006c0000000,0x00000006c0000000,0x00000006cab00000)
Metaspace used 2729K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 297K, capacity 386K, committed 512K, reserved 1048576K
从 GC 日志中可以看出,老年代均显示为ParOldGen。由此可以确定,在默认情况(即启用-XX:+UseParallelGC)和显式启用-XX:+UseParallelOldGC时,JVM 实际使用的是Parallel Old收集器,这与第二个猜想相符。
4. 禁用 Parallel Old 收集器以验证别名
接下来,我们验证第一个猜想,即PS MarkSweep是否只是一个别名,可以指代Serial Old和Parallel Old。通过使用-XX:-UseParallelOldGC参数,我们可以明确禁用Parallel Old收集器。执行命令如下,观察其效果:
java -XX:-UseParallelOldGC -XX:+PrintGCDetails GcCollectorPrinter
输出结果:
PS Scavenge
PS MarkSweep
Heap
PSYoungGen total 76288K, used 3932K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000)
eden space 65536K, 6% used [0x000000076ab00000,0x000000076aed7240,0x000000076eb00000)
from space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000)
to space 10752K, 0% used [0x000000076eb00000,0x000000076eb00000,0x000000076f580000)
PSOldGen total 175104K, used 0K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
object space 175104K, 0% used [0x00000006c0000000,0x00000006c0000000,0x00000006cab00000)
Metaspace used 2728K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 297K, capacity 386K, committed 512K, reserved 1048576K
观察到唯一的区别是,老年代区域从ParOldGen变为了PSOldGen。经过查阅资料确认,PSOldGen正是Serial Old收集器的标识。至此,我们得出了以下结论:
PS MarkSweep是一个通用别名,在不同的配置下可以指代Serial Old或Parallel Old收集器。- 在默认情况(即启用
-XX:+UseParallelGC)和显式启用-XX:+UseParallelOldGC时,JVM 都使用Parallel Old收集器。
5. 官方资料与源码验证
尽管实验结果已经很明确,但对于书籍中提及的描述,仍有必要查阅官方资料进行确认。
在 JDK 8 的官方文档中,找到了相关的说明:
Parallel compaction is enabled by default if the option
-XX:+UseParallelGChas been specified. The option to turn it off is-XX:-UseParallelOldGC.
这段描述表明,当-XX:+UseParallelGC参数被指定时,Parallel compaction (即Parallel Old)默认是开启的,除非通过-XX:-UseParallelOldGC明确关闭。这与实验结果一致,但仍未解释书籍中为何提及Serial Old。
最终,在 JDK 源码的提交记录中找到了答案。在 JDK 7u4 版本之前,-XX:+UseParallelGC确实默认使用Serial Old收集器。然而,在该版本之后,随着Parallel Old收集器的成熟,它取代了旧的收集器。因此,从 JDK 7u4 之后的 7 系列以及 JDK 8 开始,老年代的默认收集器实际上是Parallel Old。书籍中可能未能及时更新这一细节。
相关链接:
引用原文:
Server-class machine ergonomics was introduced in jdk5. If the machine upon which
the jvm is running is powerful enough (currently, at least 2 physical cores plus
at least 2gb of memory), the server jvm is invoked using the parallel scavenger
rather than the serial scavenger. Currently the old gen collector used is
serial mark-sweep-compact. Now that the parallel old gen collector is mature,
we should change to using it instead.
Issue Links
6. 总结
在 JDK 8 的 Server 模式下,默认的垃圾收集器组合是Parallel Scavenge+Parallel Old,老年代收集器并非Serial Old。
关键结论如下:
-
-XX:+UseParallelGC默认启用Parallel Old自 JDK 7u4 起(包括全部 JDK 8 版本),只要启用了
-XX:+UseParallelGC(JVM 在 server-class 机器上的默认行为),老年代就会使用Parallel Old收集器;Parallel Old的启用由-XX:+UseParallelOldGC控制,且默认为开启。 -
PS MarkSweep是一个历史遗留的通用别名无论老年代实际是
Parallel Old还是Serial Old,GarbageCollectorMXBean.getName()均返回"PS MarkSweep",这极易引发混淆。必须结合 GC 日志中的代名(ParOldGenvsPSOldGen)或 JVM 启动参数才能准确判断真实收集器。 -
书籍描述滞后于实现演进
《深入理解 Java 虚拟机》等资料中“
Parallel Scavenge + Serial Old是默认组合”的说法,适用于 JDK 7u4 之前的版本。 -
显式控制老年代行为的方法
- 使用
-XX:+UseParallelGC(默认) →Parallel Scavenge + Parallel Old - 使用
-XX:+UseParallelGC -XX:-UseParallelOldGC→Parallel Scavenge + Serial Old
- 使用
浙公网安备 33010602011771号