当数据集较小时,为什么SerialGC比ParallelGC更快?

时间:2018-02-16 13:24:23

标签: java garbage-collection jvm

通过此doc中的选择收集器章节:

  

如果应用程序的数据集很小(最大约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);
    }
}

使用SerialGC:

-Xms5m -Xmx5m -XX:+UseSerialGC -XX:+PrintGC

结果大约是50毫秒。

使用ParallelGC:

-Xms5m -Xmx5m -XX:+UseParallelGC -XX:+PrintGC

结果大约是6000毫秒。

我知道线程之间的通信在ParallelGC中可能需要一些时间,在这种情况下,为什么SerialGC比ParallelGC快得多?

3 个答案:

答案 0 :(得分:2)

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

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

几乎任何类型的并行或并发垃圾收集都必须进行大量的额外工作,以确保对堆的所有修改对其余代码都是原始的。它不仅仅停留了一段时间,而是必须停止那些依赖于特定变化的东西,然后只需要足够长的时间来执行特定的变更。然后它让代码再次开始运行,进入下一个要做出更改的点,停止依赖它的其他代码,依此类推。

答案 1 :(得分:1)

(从评论开始,但变得太长)
- 您是否使用jmh作为基准测试?
- 你在机器上有多少个线程?

如果JIT设法完成工作,那么整个循环将是一个nop,因为数据不会在任何地方使用。 逃避分析也可能已经介入,但我不这么认为,对象的数量太大了,我猜想。
换句话说,我不确定你是在测量你认为测量的东西。最好使用System.gc代替。虽然这只是一个建议,但我所知道的所有收藏家都会听从这个电话 另请注意,串行收集器和并行收集器之间的唯一区别是并行收集器使用所有可用的cpu来运行,而串行器只使用一个。它们都不是并发的,它们都是StW压缩收集器(对于旧版本)。 (请注意,所有年轻的收藏家(C4除外)都是StW复制收藏家)。
我建议使用jmh编写一个基准标记,但现在不明白如何为此编写严格的基准。
另一个建议是采用性能测试系统(如果有的话)并使用相同的方案和差异收集器运行它并分析gc日志。然后你会得到一个有意义的比较。

答案 2 :(得分:1)

<强>第一

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

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

除非您有充分的理由使用这些内存限制,否则请选择更大的内容,默认值是针对更典型的工作负载选择的,在这种情况下可能效果不佳。

<强>第二

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

为了获得更好的图片,您可以按如下方式比较计算出的标记:

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