JVM年老代达到极限,服务器挂起

24

我们的应用程序需要非常大的内存来处理大型数据,因此我们将最大堆大小增加到12GB(-Xmx)。

以下是环境详细信息:

OS - Linux 2.6.18-164.11.1.el5    
JBoss - 5.0.0.GA
VM Version - 16.0-b13 Sun JVM
JDK - 1.6.0_18
我们在我们的QA和生产环境中具有上述的环境和配置。在QA中,我们分配了最大PS Old Gen (堆内存)为8.67GB,而在生产环境中只有8GB。
对于生产环境中的一个特定工作,Old Gen Heap达到8GB后就会停滞不前,Web URL也无法访问,服务器会崩溃。但是在QA中,它也会达到8.67GB,但会执行full GC并返回至6.5GB或其它的值,这里没有出现问题。
我们无法找到解决方法,因为两个环境和两个主机上的配置完全相同。
我在这里有三个问题:
2/3的最大堆将分配给旧/ Tenured gen。如果是这样,为什么一个地方是8GB,另一个地方是8.67GB?
如何为新的和Tenure提供有效的比例(12GB)?
为什么在一个地方进行full GC而在另一个地方没有进行full GC?
任何帮助都将非常感激。谢谢。
请告诉我是否需要有关env或conf的进一步详细信息。

1
什么是任务?它持续多久?它完成了多少工作?也就是说,它产生了多少垃圾?GC配置在很大程度上受应用程序行为和暂停时间目标的驱动。 - Matt
1
@Matt - 这是一个打字错误 :( 我已经将其编辑为 -Xmx。以下是两个环境中JVM的完整cmd行 -- -Xms1003m -Xmx13312m -XX:MaxPermSize=256m -Dorg.jboss.resolver.warning=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -Dsun.lang.ClassLoader.allowArraySyntax=true - raksja
@Matt - 我不确定-d64是什么。如果你在问核心部分,我可以说它是一个64位的结尾。这是一个后端工作,每个任务需要3个小时才能完成,几乎有6个任务连续运行。最后一个任务在运行时达到了8GB的最大值,并在生产环境中出现了卡顿。 - raksja
-d64是启动64位JVM的开关。 - Matt
1
在Linux上不需要使用-d64选项。安装的Java是32位或64位,使用路径上先出现的版本。更多详情请参见http://www.oracle.com/technetwork/java/hotspotfaq-138619.html#64bit_selection。 - WhiteFang34
显示剩余2条评论
2个回答

23

针对你的具体问题:

  1. 新生代和老年代之间的默认比例可以取决于系统以及JVM确定最佳值。
  2. 要指定新生代和老年代之间的特定比例,请使用-XX:NewRatio=3
  3. 如果您的JVM挂起并且堆已满,那么它可能卡在不断进行GC操作上。

听起来你需要为生产环境配置更多内存。如果在QA环境中请求完成,那么可能只需要额外的0.67GB。但这似乎留给你的余地不多。您是否在QA与生产环境运行相同的测试?

由于您正在使用12GB,因此您必须使用64位。您可以通过使用-XX:+UseCompressedOops选项来节省64位寻址的内存开销。这通常可以节省40%的内存,因此您的12GB将得到更大的发挥。

根据您的具体情况,并发收集器可能更好,尤其是为了减少长时间的GC暂停时间。我建议尝试这些选项,因为我发现它们效果很好:

-Xmx12g -XX:NewRatio=4 -XX:SurvivorRatio=8 -XX:+UseCompressedOops
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+DisableExplicitGC
-XX:+UseCMSInitiatingOccupancyOnly -XX:+CMSClassUnloadingEnabled
-XX:+CMSScavengeBeforeRemark -XX:CMSInitiatingOccupancyFraction=68

感谢您的回复。我认为当堆更大(在我们的情况下为12GB)时,压缩Oops会降低性能。如果您在巨大的堆中使用了压缩选项,请分享您的想法。 - raksja
2
我们已经进行了许多使用24GB堆的大规模基准测试。-XX:+UseCompressedOops选项不会明显降低性能。如果您的内存受限,则它肯定可以提高性能。特别是如果它避免了内存危险地低或用尽,就像您所描述的情况一样。对于12GB而言,没有-XX:+UseCompressedOops,它将有效地相当于给它16GB。 - WhiteFang34
@Fang - 谢谢你的回复。根据你的建议,我已经单独启用了“-XX:+UseCompressedOops”选项。我会尽快向您发布结果。顺便说一句,我们计划尝试您上面建议的任何选项。如果您能详细说明上述每个选项的原因,那对我们会更有帮助。提前致谢。 - raksja
2
-XX:+UseCompressedOops 做到了这一点。我只添加了这个额外的参数,现在它可以使用 12GB 的堆内存。在 8GB 的老年代内进行进程处理,并经常发生 GC/压缩,将其减少到 6.5GB。如果您仍然坚持要求我们进行比例规定,请解释一下。谢谢。 - raksja
3
@techastute: 很高兴听到这个消息,很高兴能够帮到你。比率规范并不是必需的,它只是一种方便的方式来指定一个合理的默认值,对于相同的堆大小和不同的大堆大小都可以保持一致。我建议的所有其他选项都专注于消除长时间的GC暂停。在我们的场景中,我们发现并发收集器经常会退回到完整的GC和其他长时间的暂停状态,如果不进行调整的话。这些选项是经过长时间的持续重型Web流量的基准测试和生产实验的结果。你的情况可能有所不同。 - WhiteFang34

3
你需要获取更多数据以了解发生了什么,只有这样才能知道需要修复什么。在我看来,这意味着:
1. 获取有关垃圾收集器正在执行的详细信息,这些参数是一个很好的开始(将一些首选路径和文件代替gc.log)。
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -Xloggc:gc.log -verbose:gc
2. 重复运行,扫描 gc 日志以查找卡顿期间的输出,并将其发布回来。
3. 考虑使用 visualgc(需要在服务器上运行 jstatd),这是查看堆中各个代大小的一种非常简单的方法(尽管可能不适用于6小时!)。其中一篇随机链接解释了如何进行此设置 this one,它是 jvmstat 的一部分。
我强烈建议您进行一些阅读,以便了解所有这些开关所指的内容,否则您将盲目地尝试各种东西,而不真正理解为什么一件事情有帮助而另一件事情没有。我建议从oracle java 6 gc调优页面开始阅读,您可以在这里找到它。
我建议仅在基准性能确定后再更改选项。话虽如此,CompressedOops很可能是一个简单的胜利,您可能需要注意自6u23以来已默认启用它。
最后,您应该考虑升级jvm,6u18已经有点老了,而性能不断提高。
每个作业将需要3小时才能完成,接近6个作业一个接一个地运行。最后一个作业在运行时达到8GB的最大值,并在生产中挂起。

这些工作有关联吗?如果他们没有在处理相同的数据集,那么这真的听起来像是逐渐的内存泄漏。如果堆使用量不断增加并最终崩溃,则存在内存泄漏。建议您考虑使用 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/some/dir 来捕获堆转储(请注意,对于一个13G的堆,它将是一个很大的文件,因此请确保您有足够的磁盘空间),以便在崩溃时进行查看。然后,您可以使用 jhat 查看当时堆上的内容。


我想我们无法使用jhat分析超过512MB的转储文件。有没有其他方法可以分析这个转储文件?或者请提供一个解决方案。先感谢您了。 - raksja
你可以分析比那更大的转储文件,但这可能取决于你有多少内存。 - Matt
输入“man jhat”以获取有关如何将参数传递给JVM的信息,例如,“jhat -J-d64 -J-Xmx12G <DUMPFILE>”应该在64位JVM上运行jhat,并使用12G堆。听起来你有足够的内存来处理它。 - Matt
我甚至尝试了这个命令 jhat -J-d64 -Xmx12g -XX:-UseBiasedLocking java_pid1491.hprof#1,但它仍然抛出OOM错误。如果有任何建议,将不胜感激。谢谢。 - raksja
它没用了,Matt。我尝试了jhat的每个选项。你能推荐其他在Linux平台上使用的堆分析器吗? - raksja
显示剩余3条评论

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