调整垃圾收集以实现低延迟

我正在寻找关于如何在低延迟至关重要的环境中最好地确定年轻一代(相对于老一代)的规模的论据。

我自己的测试往往表明,当年轻一代相当大时,延迟最低(例如-XX:NewRatio <3),但是我无法将其与直觉认为年轻一代越大,垃圾应该花费的时间越多搜集。

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

内存使用量大约是50兆字节的启动时加载的长寿命对象(=数据缓存),从那里开始只创建(很多)非常短暂的对象(平均寿命<1毫秒)。

一些垃圾收集周期需要超过10毫秒才能运行…与app延迟相比看起来真的不成比例,而app延迟又是几毫秒。

对于一个产生大量短寿命垃圾并且没有长寿命的应用程序,一种可以工作的方法是一大堆几乎所有的年轻人和几乎所有的伊甸园和任期在YG集合中不止一次存活的任何东西。

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

  • 3072M堆(Xms和Xmn)
  • 128M终身(即Xmn 2944m)
  • MaxTenuringThreshold = 1
  • SurvivorRatio = 190(即每个幸存者空间是YG的1/192)
  • TargetSurvivorRatio = 90(即尽可能多地填补幸存者)

您将用于此设置的确切参数取决于工作集的稳态大小(即每次收集时有多少活动)。 这里的想法显然违反了正常的堆大小规则,但是你没有一个以这种方式运行的应用程序。 我们的想法是,应用程序主要是v短暂的垃圾和一些静态数据,所以设置jvm以便静态数据快速进入终身,然后有一个足够大的YG,以便不经常收集,从而最大限度地减少暂停的频率。 你需要反复旋转旋钮来找出适合你的大小,以及它与每个系列的暂停大小之间的平衡。 您可能会发现更短但更频繁的YG暂停是可以实现的。

你没有说你的应用程序运行了多长时间,但这里的目标是在应用程序的生命周期内根本没有终身collections。 当然这可能是不可能的,但值得瞄准。

然而,在你的情况下,它不仅仅是收集算法,它是分配内存的地方。 NUMA收集器(仅与吞吐量收集器兼容并使用UseNUMA交换机激活)利用观察对象通常纯粹由创建它的线程使用,从而相应地分配内存。 我不确定它是基于Linux的,但它在Solaris上使用MPO(内存放置优化), 其中一个GC人员博客的一些细节

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

鉴于对象分配率(可能是某种科学库?)和生命周期,你应该考虑对象重用。 执行此操作的lib的一个示例是javalution StackContext

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

您是否已启用更相关的GC设置,例如选择并发低暂停收集器算法?

从广义上讲,年轻,终身和永久的年龄需要根据您的应用程序的配置进行调整。 如果你有许多短命的物体,但是年轻的物体太小,许多物体将成为终身,迫使整个终身一代的主要集合更频繁。 同样,如果年轻人太大,那么终身职位必然更小,并且可能会强制频繁收集终身职位。

实际上,我认为你会发现,当你增加年轻一代的规模时,在次要collections和主要collections中花费的时间会有所不同,并且在某些时候是最佳的。

也许有用的是注意到在“大”性能敏感的服务器应用程序中,我发现有必要缩小年轻一代。 这是因为这些应用程序应该已经为内存分配热点进行了分析并进行了优化,因此它们只生成了很少的短期对象。 这反过来意味着年轻一代正在占据太多的堆积。

所以我想我首先要进行优化,然后看看NewRatio超过8,然后观察-verbose:gc给出的输出,看看GC和Full GC时间是如何交易的,以及它是最优的。

使用Java尝试实时应用程序时,垃圾收集调优是必不可少的,但还需要考虑其他方面(例如JIT编译器,定时器,线程,异步事件处理)。

由于似乎需要实时Java,因此Sun提供了Java实时系统规范,并提供了商业实现。 您可以在此处找到更多信息。