Java的串行垃圾收集器的性能远远优于其他垃圾收集器?

时间:2012-03-16 14:16:36

标签: java garbage-collection

我正在测试一个用Java编写的API,它可以最大限度地减少处理通过网络接收的消息的延迟。为了实现这些目标,我正在使用可用的不同垃圾收集器。

我正在尝试四种不同的技术,它们使用以下标志来控制垃圾收集:

1)Serial:-XX:+ UseSerialGC

2)并行:-XX:+ UseParallelOldGC

3)并发:-XX:+ UseConcMarkSweepGC

4)并发/增量:-XX:+ UseConcMarkSweepGC -XX:+ CMSIncrementalMode -XX:+ CMSIncrementalPacing

我在五个小时内完成了每项技术。我定期使用ManagementFactory.getGarbageCollectorMXBeans()提供的GarbageCollectorMXBean列表来检索收集垃圾所花费的总时间。

我的结果?请注意,此处的“延迟”是“我的应用程序+ API用于处理从网络中拔出的每条消息所花费的时间。”

串行:789次GC事件总计1309 ms;平均潜伏期47.45 us,中位潜伏期8.704 us,最大潜伏期1197 us

平行:1715个GC事件总计122518毫秒;平均延迟450.8 us,中位潜伏期8.448 us,最大延迟8292 us

并发:4629次GC事件总计116229 ms;平均潜伏期707.2 us,中位潜伏期9.216 us,最大潜伏期9151 us

增量:5066次GC事件总计200213 ms;平均潜伏期为515.9 us,中位潜伏期为9.472 us,最大潜伏期为14209 us

我发现这些结果是如此不可能,以至于它们与荒谬相接。有谁知道为什么我会有这样的结果?

哦,为了记录,我正在使用Java HotSpot(TM)64位服务器VM。

5 个答案:

答案 0 :(得分:18)

  

我正在开发一个有望最大化吞吐量和最小化延迟的Java应用程序

有两个问题:

  • 那些通常是矛盾的目标,所以你需要决定 重要的是什么?(你会牺牲10%的延迟来获得20%的吞吐量增益,反之亦然吗?你的目标是什么? 特定的延迟目标,除此之外它是否更快无关紧要?这样的事情。)
  • 您没有在这些
  • 周围发现任何结果

你所展示的是垃圾收集器花了多少时间。如果实际实现更多吞吐量,您可能期望看到更多时间花在垃圾收集器上。换句话说,我可以对代码进行更改,以最大限度地减少您报告的值:

// Avoid generating any garbage
Thread.sleep(10000000);

你需要弄清楚实际对你很重要。衡量一切重要的东西,然后找出权衡取舍的地方。所以第一个要做的事情是重新运行测试并测量延迟和吞吐量。您可能关心总CPU使用率(当然这与GC中的CPU不同)但是当您没有衡量主要目标时,您的结果并没有给您特别有用的信息

答案 1 :(得分:4)

我根本没有发现这一点令人惊讶。

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

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

另一点(虽然在这种情况下,可能是一个相当小的一点)是,当您处理更多数据时,通常会产生更多垃圾,因此花费更多时间进行垃圾收集。由于串行收集器在执行其工作时会停止所有其他处理,因此不仅可以快速收集垃圾,还可以防止在此期间生成更多垃圾。

现在,为什么我说在这种情况下这可能是一个小的贡献者?这很简单:串行收集器在五个小时内只用了一秒多一点。即使在那~1.3秒内没有其他任何事情完成,这是五小时的这么小的百分比,它可能没有对你的整体吞吐量产生任何太大的(如果有的话)真正的差异。

总结:串行垃圾收集的问题并不在于它总是花费过多的时间 - 如果它恰好在您需要快速响应时停止了正确的世界,那将是非常不方便的。同时,我应该补充一点,只要你的收集周期很短,这仍然是相当小的。理论上,其他形式的GC主要限制你的最坏情况,但事实上(例如,通过限制堆大小)你通常也可以通过串行收集器限制你的最大延迟。

答案 2 :(得分:2)

Twitter工程师在2012年QCon Conference就此话题进行了精彩的演讲 - 您可以观看here

它讨论了Hotspot JVM内存和垃圾收集(Eden,Survivor,Old)中的各种“代”。特别注意ConcurrentMarkAndSweep中的“Concurrent”仅适用于Old代,即暂停一段时间的对象。

短期对象来自“Eden”代的GCd - 这很便宜,但无论您选择哪种GC算法,它都是“停止世界”的GC事件!

建议是首先调整年轻一代,例如分配大量的新伊甸园,这样就有更多的机会让物品变得年轻并且便宜地被收回。使用+ PrintGCDetails,+ PrintHeapAtGC,+ PrintTenuringDistribution ...如果你获得超过100%的幸存者,那么没有空间,所以对象很快被提升为旧 - 这是坏的。

在调整Old generatiohn时,如果延迟是首要任务,建议首先尝试使用自动调谐的ParallelOld(+ AdaptiveSizePolicy等),然后尝试CMS,然后尝试新的G1GC。

答案 3 :(得分:0)

你不能说一个GC比另一个好。这取决于您的要求和您的申请。

但如果您想最大化吞吐量并最大限度地减少延迟:GC就是您的敌人!你根本不应该调用GC,也试着阻止JVM调用GC。

使用serial并使用对象池。

答案 4 :(得分:0)

通过串行收集,一次只能发生一件事。例如,即使有多个CPU 可用,只有一个用于执行收集。当使用并行收集时,任务 垃圾收集分为几个部分,这些子部分在不同的时间同时执行 的CPU。同时操作使得收集能够以更快的速度完成,但代价是 一些额外的复杂性和潜在的碎片。

虽然串行GC仅使用一个线程来处理GC,但并行GC使用多个线程来处理GC,因此速度更快。当有足够的内存和大量内核时,此GC非常有用。它也被称为“吞吐量GC。”