调整JVM(GC)以实现高响应的服务器应用程序

我在Linux 64bit上运行一个具有8个核心CPU和6 GB内存的应用程序服务器。

服务器必须具有高响应性。

经过一番检查后,我发现在服务器上运行的应用程序创建了相当多的短期对象,并且只有大约200~400 MB的长寿命对象(只要没有内存泄漏)

阅读http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html后,我使用这些JVM选项

-server -Xms2g -Xmx2g -XX:MaxPermSize=256m -XX:NewRatio=1 -XX:+UseConcMarkSweepGC 

结果:次要GC需要0.01~0.02秒,主要GC需要1~3秒,小GC不断发生。

如何进一步改进或调整JVM?

堆大小? 但GC需要更多时间吗?

更大的NewSize和MaxNewSize(适合年轻一代)?

其他collections家? 并行GC?

让主要GC更频繁地进行是一个好主意吗? 如何?

结果:次要GC需要0.01~0.02秒,主要GC需要1~3秒,小GC不断发生。

除非您报告暂停,否则我会说CMS收集器正在执行您要求它执行的操作。 根据定义,CMS将使用比串行和并行收集器更大的CPU百分比。 这是您为低暂停时间支付的罚金。

如果你看到1到3秒的暂停时间,我会说你需要做一些调整。 我不是专家,但看起来你应该从默认值92减少CMSInitiatingOccupancyFraction的值开始。

增加堆大小将提高GC的“吞吐量”。 但是,如果您的问题是长时间暂停,增加堆大小可能会使问题变得更糟。

小心….如果你不谨慎,GC可能是一个毛茸茸的主题。 在任何运行时(JVM for Java / CLR for .Net)中都会发生几个进程。 通常存在记忆的早期阶段优化(Young Generational Garbage Collection / Young Gen GC&Old Generational Garbage Collection / Old Gen GC)。 年轻的gc定期发生,通常归因于你较小的停顿/打嗝。 当看到漫长的“停止世界”暂停时,旧的gc通常是正在发生的事情。

你可能会问为什么? 您使用运行时/ JVM暂停的原因是,当运行时清理堆时,它必须经历所谓的相变。 它会停止运行应用程序的线程,以便标记和交换指针以优化可用内存。 Yong gen更快,因为它主要释放只是暂时的物体。 但是,旧的gen会对堆上的所有对象进行评估,当你的内存不足时,它将释放出大量需要的内存。

为什么要小心? 旧的gen在暂停时间中呈指数级变差,您使用的堆越多。 在Java堆总大小为2-4 GB的情况下,您应该可以在Java 6(JDK 1.6+)等现代运行时使用。 一旦超出该threashold,您将看到暂停时间呈指数增长。 我遇到了一些必须重新启动服务器的客户端 – 在极少数情况下,堆很大,GC暂停时间可能比完全重启更长。

有一些非常酷的新工具可以让你在评估GC是否是你的痛苦方面具有领先优势。 JHiccup是一个,它是免费的azulsystems网站。 此时我认为它仅适用于Linux。 他们还有一个JVM,它具有重新构建的GC算法,可以无间断地运行……但是如果你使用非关键应用程序进行单一服务器部署,那么它可能不具成本效益(一个不是免费的)。

总结一下 – 如果您的运行时/ JVM / CLR堆小于2 GB,则添加更多内存将有所帮助。 一定要给自己一些开销。 如果可能的话,你永远不想达到100%的堆大小/内存大小。 那是长时间停顿最长的时候。 给自己额外的20%+记忆超过你认为你需要的。 这样,您就有了GC算法的空间来移动对象以进行优化。 如果你计划扩大…有一个工具可以修复大约1990年的JVM技术(Azul Systems Zing JVM),但它不是免费的。 他们确实提供了一个用于诊断GC问题的开源工具。 JVM(我已经尝试过)也有一个非常酷的线程级别可见性工具,它允许您在没有开销的情况下报告生产中的任何泄漏,错误或锁定(一些技巧可以卸载JVM已经处理的数据和时间戳)。 这节省了大量的开发测试时间……但同样,不是小应用程序。

保持低于4 GB。 给予额外的空间。 如果您愿意,可以打开这些标志来监视GC for Java / JVM:

 java -verbose:gc myProgram java -Xloggc:D:/log/myLogFile.log -XX:+PrintGCDetails myProgram 

您可以尝试Hotspot使用的其他收集器。 不止一个。

如果你在Linux上,请继续尝试JHiccup工具。 这是免费的。

您可能有兴趣尝试低暂停Garbage-First收集器而不是并发标记扫描(尽管它对于所有集合来说不一定更具性能,但它应该具有更好的最坏情况)。 它是由-XX:+UseG1GC启用的,应该是非常棒的,但你可能想在生产中使用它之前给它一个全面的评估。 从那以后它可能已经有所改进,但是在一年前看起来有点像马车,如JDK 1.6.x G1的经验(“垃圾优先”)

如果你有足够的cpu,那么垃圾收集与你的程序并行运行是完全没问题的。

你想要的是,绝对肯定你不会遇到垃圾收集PAUSES你的主程序的情况。

您是否尝试过不说明任何标志,除了说您想要服务器VM(对于Sun JVM),然后将您的服务器置于高负载下以查看它的行为方式? 只有这样你才能看到,如果你通过修改选项得到任何改进。

这实际上听起来像吞吐量应用程序,应该使用吞吐量收集器。 我会平衡新一代的大小,使其足够大,以至于不经常使用GC而且足够小以防止长时间暂停。 对我来说,20ms听起来像是一个很长的次要GC。 我还怀疑你的幸存者空间太大,只是被浪费了。 如果你没有多少幸存下来的老生活,你不应该在你的小型GC中幸存下来。

最后,您应该使用jvmstat和VisualGC来真正了解您的应用程序如何使用内存。

对于高响应的服务器应用程序,我认为您希望看到主要的GC发生频率较低。 以下是有用的参数列表。

-XX:+ CMSParallelRemarkEnabled
-XX:+ CMSScavengeBeforeRemark
-XX:+ UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction = 50
-XX:CMSWaitDuration = 300000
-XX:GCTimeRatio = 40

只要您的应用程序内存不足,较大的堆大小可能无助于低暂停。

较大的NewSize和MaxNewSize对吞吐量有帮助,可能对低停顿没有帮助。 如果选择采用这种方法,可以考虑通过设置-XX:GCTimeRatio lower来为GC线程提供更多的执行时间。 关键是要记住在调整JVM时要采取整体措施。

我认为以前的海报错过了一些非常明显的东西 – 彼尔姆一代的尺寸太低了。 如果系统使用200到400 MB作为永久生成 – 那么最好将Max Perm Gen设置为400 MB。 PerGen大小也应设置为相同的值。 然后,你永远不会用完永久性发电空间。

目前看起来JVM必须花费大量时间将对象移入和移出Permanent Generation。 这可能需要时间。 JVM尝试为Java对象分配连续的内存区域 – 由于硬件级别的特性,这加速了内存访问。 为了做到这一点,在内存中有足够的缓冲区是非常有帮助的。 如果永久生成几乎已满,则必须拆分新发现的永久对象,或者必须对现有对象进行洗牌。 这是触发完整GC的原因,也会导致GC完全长时间停顿。

问题表明已经测量了永久代的尺寸 – 如果没有这样做,则应使用工具进行测量。 这些工具处理由JVM生成的日志,并打开verboseGC选项。

此基本改进可能不需要上面列出的所有标记和扫描选项。

人们将GC选项作为解决方案,而不评估它们在实际使用中的成熟程度。