如何调整Java GC以适应基准测试?

4

我正在尝试调整垃圾回收器,以便在短基准测试期间不触发它。

为此,我尝试将年轻代设得尽可能大,并确保创建的垃圾少于伊甸区大小。

我使用了以下参数:

java -Xmx1g -Xms1g -Xmn1g -XX:NewSize=1000m -XX:MaxNewSize=1g -XX:SurvivorRatio=10000 -verbose:gc Main

但是出于某种原因,当虚拟机内存仅为300M时,我仍然看到[Full GC]日志,是否有我遗漏的JVM调优措施?


1
没有看到你的代码,很难说。也许你可以提供一个简短的可运行示例,展示这种(意外的)行为,并在这里发布?这将使我们能够进行实验,并理解到底发生了什么。 - NPE
谢谢,我会尝试创建一个。这是否意味着您认为我通常可以实现预期的目标? - Guy Korland
原则上是可以的(前提是你的代码不会产生足够多的垃圾需要进行回收)。 - NPE
正如我在问题中所写的,当我的内存使用量达到1G时,我会看到完整的垃圾回收(GC)在300M时启动。 - Guy Korland
这些是我们看到的GC日志:[Full GC 444212K->53612K(1048512K),0.1674871秒] [Full GC 98878K->53600K(1048512K),0.1353826秒] [Full GC 117736K->53600K(1048512K),0.1126983秒] [Full GC 96769K->53600K(1048512K),0.1046997秒] [Full GC 202470K->53600K(1048512K),0.1597075秒] [Full GC 56930K->53600K(1048512K),0.1134397秒] [Full GC 267471K->53756K(1048512K),0.1372057秒] [Full GC 54990K->53756K(1048512K),0.1068497秒] [Full GC 160691K->53912K(1048512K),0.1366148秒] [Full GC 122243K->53913K(1048512K),0.1218144秒] [Full GC 360042K->53922K(1048512K),0.1614036秒] - Guy Korland
3个回答

2

如果你的tenured space已经填满(大对象直接放入tenured space),那么你可能需要进行full GC。

通常我会尝试使用4-24 GB的eden大小,但确保tenured space的大小为0.5到2 GB。


2
您的幸存者配额非常重要,如果有10,000,则表示JVM一个幸存者空间大小为eden的1/10000。
根据文档
如果幸存者空间太小,则复制集合会直接溢出到老年代。如果幸存者空间太大,则它们将被无用地浪费。在每次垃圾回收时,虚拟机选择对象可以被复制多少次才进入老年代的阈值。此阈值被选择为保持幸存者半满。命令行选项-XX:+PrintTenuringDistribution可用于显示此阈值以及新一代中对象的年龄。它还可用于观察应用程序的生命周期分布。
因此,显然,当幸存者空间比例如此之低时,您的对象直接存储在老年代中。
当老年代已满时,将发生主要收集,这就是为什么在日志中看到[Full GC...]的原因。
如果您想使年轻代更大,请使用:
-XX:NewRatio=1

这意味着Eden和Survivor空间的总大小将是堆大小的一半。(我猜你不能有更大的年轻代)
此外,如果您已经设置了-Xmn,则不必设置-XX:NewSize,因为自1.4以来,-Xmn就是相同的东西。我猜您不想使用-XX:MaxNewSize限制年轻代,因为默认值是无限制的。但是,将年轻代的大小设置为与最大堆大小相同的值意味着您不允许老年代的空间,因此我猜JVM会调整代的大小。
总之,还有一些情况可以直接分配到老年代中。(对象是否会直接分配到老年代中?
  • 如果在年轻代中分配失败并且对象是不包含任何对对象的引用的大型数组,则可以直接将其分配到老年代中。在某些特定情况下,这种策略旨在通过从老年代进行分配来避免对年轻代进行收集。 阈值大小为64k个字。

我的希望是,由于Eden区如此之大,它永远不会被收集。因此,垃圾回收器永远不会尝试将对象放入Survivor区域。 - Guy Korland
@GuyKorland 我做了一次编辑。我希望这些信息会有帮助 ;) - alain.janinm
感谢上一版,有没有办法控制“阈值大小为64k个单词”? - Guy Korland
实际上,64k的大小取决于您使用的JVM版本和收集器。如果您阅读第二个链接中给出的完整答案,您将看到此阈值已提高到young gen的大小,并且可以设置该阈值的选项已默认设置为无限制。所以我想你对此无能为力。只需为年轻一代分配正确的大小,避免超过堆大小的一半即可。 - alain.janinm

1

如果您正在释放大量对象,则GC仍将运行,这不仅仅是等到您使用了所有分配的空间,如果这样做,它会在运行时导致严重的GC抖动。

您应该查看您的对象处理方式,以及是否可以更有效地使用。查看LMAX disrupter模式,了解如何通过更新对象值而不是释放和替换对象本身来重用对象的一些想法。

话虽如此,这是一篇关于调整GC的相当不错的文章 - http://developer.amd.com/documentation/articles/pages/4EasyWaystodoJavaGarbageCollectionTuning.aspx


我知道,但我真的不想在生产环境中使用这些参数运行,而是将GC暂停与应用逻辑隔离开来。 - Guy Korland
1
如果情况是这样,更有理由看看你如何处理对象。如果你有大量的释放操作,那么你将会有高GC。你的应用程序中对象是如何引用的?你是否每次需要时都创建处理类而不是重用它们?如果问题是你有很多新数据进入应用程序,但只需要短时间内使用,那么请尝试创建一组永远不会被释放的对象,用于保存进入应用程序的原始值,并不断重复使用它们。 - codeghost
您应该在应用程序设置中保留GC,并像往常一样平均运行时间 - 这将考虑到被基准测试代码创建的平均GC负载。 - Louis Wasserman
我们在基准测试中这样做,毫无疑问这是衡量事物的唯一真实方式。然而,回答我的问题,你知道如何设置GC吗? - Guy Korland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接