Java 当数据集很小时,为什么SerialGC比ParallelGC快?

Java 当数据集很小时,为什么SerialGC比ParallelGC快?,java,garbage-collection,jvm,Java,Garbage Collection,Jvm,从本章的“选择收集器”一章: 如果应用程序有一个小的数据集(高达大约100 MB),则选择带有选项-XX:+UseSerialGC的串行采集器 串行收集器使用单个线程来执行所有垃圾收集工作,这使得它相对高效,因为线程之间没有通信开销 我进行了一些测试 public class Example { public static void main(String[] args) throws Exception { long start = System.currentTim

从本章的“选择收集器”一章:

如果应用程序有一个小的数据集(高达大约100 MB),则选择带有选项-XX:+UseSerialGC的串行采集器

串行收集器使用单个线程来执行所有垃圾收集工作,这使得它相对高效,因为线程之间没有通信开销

我进行了一些测试

public class Example {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        Map<Integer, Object> map = new HashMap<>();
        for (int count = 0; count < 60000; count++) {
            map.put(count, new Object());
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}
结果大约是50毫秒

使用ParallelGC:

-Xms5m -Xmx5m -XX:+UseParallelGC -XX:+PrintGC
结果是大约6000毫秒


我知道线程之间的通信在ParallelGC中可能需要一些时间,在这种情况下,SerialGC比ParallelGC快得多还有其他原因吗?

除了线程之外,我能想到的另一个原因是:

当串行垃圾收集正在运行时,其他任何东西都不能运行(也称为“停止世界”)。这有一个很好的观点:它将垃圾收集的工作量保持在最低限度


几乎任何类型的并行或并发垃圾收集都必须做相当多的额外工作,以确保对堆的所有修改在代码的其余部分看来都是原子的。它必须停止那些依赖于某一特定变化的事情,然后停止足够长的时间,以执行该特定变化,而不是仅仅停止一段时间。然后,它让代码再次开始运行,到达下一个要进行更改的点,停止依赖它的其他代码,等等。

除了线程之外,我能想到的另一个原因是:

当串行垃圾收集正在运行时,其他任何东西都不能运行(也称为“停止世界”)。这有一个很好的观点:它将垃圾收集的工作量保持在最低限度

几乎任何类型的并行或并发垃圾收集都必须做相当多的额外工作,以确保对堆的所有修改在代码的其余部分看来都是原子的。它必须停止那些依赖于某一特定变化的事情,然后停止足够长的时间,以执行该特定变化,而不是仅仅停止一段时间。然后,它让该代码再次开始运行,到达下一个要进行更改的点,停止依赖它的其他代码,依此类推。

(它开始时是注释,但变得太长了)
-您是否使用jmh作为基准
-机器上有多少个硬件线程

如果JIT成功地完成了工作,那么整个循环将是一个nop,因为数据没有在任何地方使用。 逃逸分析可能也介入了,但我不这么认为,我猜物体的数量太多了。
换句话说,我不确定你是否在测量你认为正在测量的东西。最好改用
System.gc
。虽然这只是一个建议,但据我所知,所有的收藏家都会遵守这一要求。
还要注意,串行采集器和并行采集器之间的唯一区别是并行采集器使用所有可用cpu来运行,而串行采集器仅使用一个cpu。它们都不是并发的,它们都是StW压缩收集器(对于旧版本)。(请注意,所有young gen收集器(C4除外)都是StW复制收集器)。
我建议改为使用jmh编写基准,但目前还不清楚如何为此编写严格的基准。
另一个建议是使用一个性能测试系统(如果您有),并使用相同的场景和分析gc日志的不同收集器运行它。然后你会得到一个有意义的比较。

(开始时是注释,但变得太长了)
-您是否使用jmh作为基准
-机器上有多少个硬件线程

如果JIT成功地完成了工作,那么整个循环将是一个nop,因为数据没有在任何地方使用。 逃逸分析可能也介入了,但我不这么认为,我猜物体的数量太多了。
换句话说,我不确定你是否在测量你认为正在测量的东西。最好改用
System.gc
。虽然这只是一个建议,但据我所知,所有的收藏家都会遵守这一要求。
还要注意,串行采集器和并行采集器之间的唯一区别是并行采集器使用所有可用cpu来运行,而串行采集器仅使用一个cpu。它们都不是并发的,它们都是StW压缩收集器(对于旧版本)。(请注意,所有young gen收集器(C4除外)都是StW复制收集器)。
我建议改为使用jmh编写基准,但目前还不清楚如何为此编写严格的基准。
另一个建议是使用一个性能测试系统(如果您有),并使用相同的场景和分析gc日志的不同收集器运行它。然后你会得到一个有意义的比较。

首先

5MB堆本质上是退化情况。通过大量调整,JVM可以在这种情况下工作,但默认情况下很容易出现问题

5MB堆大小并不意味着您可以分配5MB的对象,因为在任何给定时间,年轻的一代都会部分为空,换句话说,它会消耗您的内存预算

除非您有充分的理由使用这些内存限制,否则请选择更大的内存限制,默认值是为更典型的工作负载选择的,在这些情况下可能无法正常工作

设置JVM参数也可能会更改其他默认参数,因此您设置的参数并不是唯一会更改的参数

要获得更好的图片,您可以按如下方式比较计算的标志:

diff -U 0 <(java -Xms5m -Xmx5m -XX:+UseSerialGC -XX:+PrintFlagsFinal) <(java -Xms5m -Xmx5m -XX:+UseParallelGC -XX:+PrintFlagsFinal)
diff-u0

5MB堆本质上是退化情况。通过大量调整,JVM可以
diff -U 0 <(java -Xms5m -Xmx5m -XX:+UseSerialGC -XX:+PrintFlagsFinal) <(java -Xms5m -Xmx5m -XX:+UseParallelGC -XX:+PrintFlagsFinal)