调整垃圾收集以降低延迟


问题内容

我正在寻找有关如何在低延迟至关重要的环境中最大程度地调整年轻一代(相对于老一代)的争论。

我自己的测试倾向于表明,当年轻一代相当大时(例如-XX:NewRatio
<3),延迟是最低的,但是我不能直觉地认为年轻一代越大,进行垃圾处理的时间就越多收集。

该应用程序在Linux 64位jdk 6上运行。

内存使用量大约是启动时加载的50万兆个长寿命对象(=数据缓存),并且从那里仅创建(很多)寿命很短的对象(平均寿命<1毫秒)。

某些垃圾收集周期要花费10毫秒以上的时间才能运行……与应用程序延迟相比,这看起来确实不成比例,而应用程序延迟又是几毫秒。


问题答案:

对于产生大量短寿命垃圾且没有长寿命垃圾的应用程序而言,一种可行的方法是堆满一大堆东西,几乎所有年轻一代,几乎所有伊甸园和任职期都超过一个的YG集合。

例如(假设您有一个32位的jvm)

  • 3072M堆(Xms和Xmn)
  • 使用期限为128M(即Xmn 2944m)
  • MaxTenuringThreshold = 1
  • SurvivorRatio = 190(即每个幸存者空间是YG的1/192)
  • TargetSurvivorRatio = 90(即尽可能地填充那些幸存者)

用于此设置的确切参数取决于您的工作集的稳态大小(即,每次收集时还剩下多少)。这里的想法显然违背了正常的堆大小调整规则,但是您没有一个具有这种行为的应用程序。这种想法是,该应用主要是短期垃圾和少量静态数据,因此请设置jvm,以使静态数据能够快速进入使用期,然后具有足够大的YG,以至于它不会经常被收集,从而将其最小化暂停的频率。您需要反复旋转旋钮才能确定适合您的尺寸,以及如何与每个系列的暂停尺寸相平衡。例如,您可能会发现更短但更频繁的YG暂停是可以实现的。

您没有说您的应用程序可以运行多长时间,但是这里的目标是在应用程序的生命周期内完全没有永久性集合。当然这可能是不可能的,但值得针对。

但是,在您的情况下,不仅重要的是收集算法,还分配了内存。NUMA收集器(仅与吞吐量收集器兼容并由UseNUMA开关激活)利用了这样的观察:对象通常仅由创建该对象的线程使用,从而相应地分配内存。我不确定它在Linux中基于什么,但是它在Solaris上使用MPO(内存放置优化),有关GC博客之一的一些详细信息

由于您使用的是64位jvm,因此请确保同时使用CompressedOops。

给定对象分配的速率(可能是某种科学的自由吗?)和生存期,那么您应该考虑对象重用。lib的一个示例就是javalution StackContext

最后值得一提的是,GC暂停不是唯一的STW暂停,您可以使用6u21抢先体验版本运行,该版本对PrintGCApplicationStoppedTime和PrintGCApplicationConcurrentTime开关(在全局安全点和这些安全点之间的时间有效打印时间)进行了一些修复。您可以使用tracesafepointstatistics标志来了解导致它需要安全点的原因(也就是任何线程都没有执行字节码)。