为高响应服务器应用程序调整JVM(GC)

8
我正在运行一个安装在Linux 64位系统上、拥有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

结果:小垃圾回收占用0.01~0.02秒,大垃圾回收需要1~3秒。小垃圾回收会不断发生。
如何进一步改进或调整JVM?
增加堆大小?但会增加GC的时间吗?
增加年轻代的NewSize和MaxNewSize?
尝试其他的回收器,例如并行GC?
让主要的GC更频繁发生是一个好主意吗?如果是,应该如何做到?
7个回答

8
结果:小型GC需要0.01~0.02秒,大型GC需要1~3秒,小型GC会不断发生。
除非您报告了暂停,否则我会说CMS收集器正在按照您的要求执行。按定义,CMS将使用比串行和并行收集器更高的CPU百分比。这是您为低暂停时间所付出的代价。
如果您看到1到3秒的“暂停”时间,我会建议您进行一些调整。虽然我不是专家,但看起来您应该从默认值92开始减少CMSInitiatingOccupancyFraction的值。
增加堆大小将提高GC的“吞吐量”。但是,如果您的问题是长时间暂停,增加堆大小可能会使问题变得更糟。

CMSInitiatingOccupancyFraction 的默认值为 -1,而 CMS 使用的 CMSTriggerRatio 默认值为 80。 - shaoyihe
@shaoyihe - 请提供参考资料,以便我更新答案。 - Stephen C
我看到了答案时间。它是2010年的答案,也许有些东西已经改变了。 - shaoyihe
是的...这也是我的想法。但我不能在稍后进行研究。 - Stephen C

3
小心……如果不谨慎,GC可能是一个棘手的话题。在任何运行时(Java的JVM/.Net的CLR)中,都会发生几个过程。通常会对内存进行早期优化(Young Generational Garbage Collection / Young Gen GC和Old Generational Garbage Collection / Old Gen GC)。年轻代垃圾回收经常发生,并且通常归因于您更小的暂停/抽搐。老年代垃圾回收通常是在看到长时间的“停止世界”暂停时正在进行的操作。
你可能会问为什么?当运行时清理堆时,您的运行时/JVM会出现暂停的原因是它必须经过所谓的相变。它停止运行应用程序的线程,以标记和交换指针以优化可用内存。年轻代更快,因为它主要释放只是临时对象。然而,老年代评估堆上的所有对象,并在内存耗尽时启动以释放非常需要的内存。
为什么要小心?旧的代在堆使用量增加时,暂停时间呈指数级增长。当总堆大小在2-4 GB时,你应该可以在像Java 6(JDK 1.6+)这样的现代运行时上正常运行。一旦超过这个阈值,你将看到暂停时间呈指数级增长。我曾经遇到一些客户需要重新启动服务器,因为在某些罕见情况下,如果堆很大,则GC暂停时间可能比完全重新启动还要长。
现在有一些新工具非常酷,可以帮助你评估GC是否是问题。其中之一是JHiccup,它可以从azulsystems网站免费获取。目前我认为它只适用于Linux。他们还有一个JVM,具有重新构建的GC算法,可以无暂停地运行...但如果你在单个服务器部署中使用非关键应用程序,则可能不划算(该软件不免费)。
总之,如果您的运行时/JVM/CLR堆小于2GB,则增加更多内存将有所帮助。请确保给自己留一些余地。如果可能,永远不要达到100%的堆大小/内存大小。那时长时间暂停最长。给自己比您认为需要的多20%以上的内存。这样,您就有了优化移动对象的GC算法的空间。如果您曾经计划扩大规模......有一个工具可以修复约1990年的JVM技术(Azul Systems Zing JVM),但它不是免费的。他们提供了一个开源工具来诊断GC问题。JVM(我尝试过)还具有非常酷的线程级别可见性工具,可让您在生产中报告任何泄漏、错误或锁定而无需额外负担(使用JVM已经处理的数据和时间戳的某些技巧)。这节省了大量开发测试时间......但对于小型应用程序来说不适用。
保持在4GB以下。给予额外的余地。如果您想的话,可以打开这些标志来监视Java/JVM的GC:
java -verbose:gc myProgram
java -Xloggc:D:/log/myLogFile.log -XX:+PrintGCDetails myProgram

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

如果您使用 Linux,请尝试 JHiccup 工具。它是免费的。


2

如果你想尝试低暂停的Garbage-First收集器,而不是并发标记清除(虽然它不一定对所有收集都更具性能优势,但据说最坏情况下表现更好)。它通过-XX:+UseG1GC启用,应该非常棒,但在生产环境中使用之前可能需要进行彻底评估。它可能已经得到改进,但在一年前似乎有些bug,如JDK 1.6.x G1(“Garbage First”)的使用经验所示。


我想指出,在许多情况下,Azul Zing jvm的性能更高。他们在应用程序运行时在后台进行垃圾回收。非常酷的东西。再次强调,它不是免费的,但对于那些希望摆脱调整JVM的需要的人来说,这个可以做到。我认为他们称之为他们的C4收集器(并发,连续,压缩,收集器?)。Mike McCandless最近对Apache Lucene / Solr进行了基准测试。在可扩展性方面取得了很好的结果:http://blog.mikemccandless.com/2012/07/lucene-index-in-ram-with-azuls-zing-jvm.html 我一直在关注这个,因为它改变了游戏规则。 - Zack Jannsen

1

如果你有足够的CPU,那么垃圾回收与你的程序并行运行是完全可以接受的。

你想要的是确保绝对不会出现垃圾回收暂停主程序的情况。

你试过只声明想要服务器VM(对于Sun JVM),然后使服务器承受重负来测试它的表现吗?只有这样,你才能看到是否从调整选项中获得任何改进。


1

这似乎是一个吞吐量应用程序,应该使用吞吐量收集器。我会平衡新生代的大小,使其足够大,以免过于频繁地进行垃圾回收,但又足够小,以防止长时间的暂停。对我来说,20毫秒听起来像是一个很长的次要GC。我还怀疑你的幸存者空间设置得太大了,只是在浪费资源。如果你没有太多的对象存活到老年代,那么在次要GC中存活的对象也不应该太多。

最后,你应该使用jvmstat和VisualGC来真正感受你的应用程序如何使用内存。


1

对于高响应的服务器应用程序,我认为您希望较少发生主要GC。以下是一些有用的参数列表:

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

增加堆大小可能不会减少暂停时间,只要您的应用程序没有耗尽内存。

增加NewSize和MaxNewSize可以提高吞吐量,但可能不会减少暂停时间。如果您选择采取这种方法,可以通过设置-XX:GCTimeRatio更低来给GC线程更多的执行时间。重要的是要记住在调整JVM时要考虑全面。


0

我认为之前的帖子漏掉了一个非常明显的问题- Perm Generation 大小太低了。如果系统使用 200 到 400 MB 作为永久代,那么最好将 Max Perm Gen 设置为 400 MB。PerGen 大小也应该设置为相同的值。这样,您就永远不会耗尽 Permanent Generation 空间。

目前看来,JVM 不得不花费大量时间在 Permanent Generation 中移动对象。这可能需要一些时间。JVM 尝试为 Java 对象分配连续的内存区域- 这加快了由于硬件级别特性而导致的内存访问速度。为了做到这一点,在内存中拥有足够的缓冲非常有帮助。如果 Permanent Generation 几乎已满,则新发现的永久对象必须被拆分或现有对象必须被洗牌。这就是触发完整 GC 的原因,以及导致长时间的完整 GC 暂停的原因。

问题说明已经测量了 Permanent Generation 大小- 如果还没有进行测量,则应使用工具进行测量。这些工具处理由打开 verboseGC 选项生成的 JVM 日志。

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

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


1
我认为你对永久代有些困惑。它不保存对象,而是保存类定义、类元数据等。 - matthew p.

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