我正在运行一个创建和忘记大量对象的应用程序,长现有对象的数量确实增长缓慢,但与短期对象相比,这是非常少的。这是一个具有高可用性要求的桌面应用程序,需要每天24小时打开。大部分工作都是在一个线程上完成的,这个线程只会使用它可以获得它的所有CPU。
在过去,我们在重载下看到了以下情况:
使用的堆空间缓慢上升,因为垃圾收集器收集的内存少于新分配的内存量,使用的堆大小缓慢增长并最终接近指定的最大堆。此时,垃圾收集器将大量启动,并开始使用大量资源来防止超过最大堆大小。这样可以减慢应用程序的速度(缓慢10倍),此时GC大部分时间会在几分钟后成功清理垃圾或者失败并抛出OutOfMemoryException,这两种情况都不可接受。
使用的硬件是四核处理器,至少4GB内存运行64位Linux,如果需要,我们可以使用所有这些。目前,该应用程序大量使用单个核心,该核心大部分时间都在运行单个核心/线程。其他核心大多是空闲的,可用于垃圾收集。
我有一种感觉,垃圾收集器应该在早期阶段更积极地收集,远在它耗尽内存之前。我们的应用程序没有任何吞吐量问题,低暂停时间要求比吞吐量更重要,但远不如不接近最大堆大小重要。如果单个繁忙线程仅以当前速度的75%运行是可以接受的,只要这意味着垃圾收集器可以跟上创建的步伐。简而言之,性能的稳定下降优于我们现在看到的突然下降。
我已经仔细阅读了Java SE 6 HotSpot [tm]虚拟机垃圾收集调整,这意味着我很好地理解了这些选项,但是我仍然觉得很难选择正确的设置,因为我的要求与文中讨论的内容有点不同。
目前我正在使用带有选项-XX:GCTimeRatio=4的ParallelGC。这比时间比的默认设置好一点,但我感觉GC允许运行的设置比它更多。
为了监控,我主要使用jconsole和jvisualvm。
我想知道您为上述情况推荐的垃圾收集选项。另外,我可以看看哪个GC调试输出更好地理解瓶颈。
编辑:
我理解这里有一个非常好的选择就是创造更少的垃圾,这是我们真正考虑的事情,但是我想知道我们如何通过GC调整来解决这个问题,因为这是我们可以更容易做到的事情并推出更多比改变大量的源代码快。此外,我运行了不同的内存分析器,我了解垃圾的用途,我知道它包含可以收集的对象。
我在用:
1 2 3
| java version "1.6.0_27-ea"
Java (TM ) SE Runtime Environment (build 1.6.0_27 -ea -b03 )
Java HotSpot (TM ) 64-Bit Server VM (build 20.2-b03, mixed mode ) |
使用JVM参数:
1
| -Xmx1024M and -XX:GCTimeRatio=4 |
编辑回复Matts评论:
大多数内存(和cpu)用于构建表示当前情况的对象。当情况迅速变化时,其中一些将被立即丢弃,如果没有更新进入一段时间,其他一些将具有中等寿命。
-
考虑发布您现在使用的所有VM参数。
-
我正在使用Java 6,参数是ony -Xmx1024M和-XX:GCTimeRatio = 4(Jvm将自身检测为服务器并使用并行GC)。 该应用程序也将主要运行在200M(实际上它似乎运行得更好,因为它将被触发更快地清理,然后它没有更多的工作洪水)。
-
你能在"年轻收藏品数量"一词中定义"中等生命时间"吗? 听起来"如果暂时没有更新"意味着在此期间对象分配的速度会急剧减慢,在这种情况下,年轻集合之间的间隔应该相应地延伸。 如果是这样,相同(小)的MTT可能没问题。
您没有提到正在运行的JVM的哪个版本,这是至关重要的信息。你也没有提到应用程序运行的时间长短(例如,它是一个工作日的长度?一周?更少?)
其他几点
如果你不断地将物品泄漏到终身,因为你的分配速度比你的年轻人可以被扫描的速度快,那么你的世代就不合适了。您需要对应用程序的行为进行一些正确的分析才能正确调整它们的大小,您可以使用visualgc进行此操作。
吞吐量收集器设计为接受单个大停顿,而不是许多较小的暂停,其好处是它是一个紧凑的收集器,它可以实现更高的总吞吐量
CMS的存在是为了服务于频谱的另一端,即更多更小的暂停但总吞吐量更低。缺点是它没有压缩,所以碎片可能是一个问题。碎片问题在6u26改进了,所以如果你不在那个版本上,那么可能是升级时间。请注意,您所注意到的"流失到终身"效果会加剧碎片问题,并且,如果时间过长,这将导致促销失败(也就是计划外的完整gc和同事STW暂停)。我之前在这个问题上写过这个问题的答案
如果你正在运行一个带有> 4GB RAM和最近JVM的64位JVM,请确保你-XX:+UseCompressedOops否则你只是浪费空间,因为64位JVM占用相同工作负载的32位JVM空间的1.5倍它(如果你不是,升级以获得更多RAM)
您可能还想阅读我在这个主题上写的另一个答案,这个答案将用于评估您的幸存者空间和范围。伊甸园恰当。基本上你想要实现的是;
-
伊甸园足够大,不经常收集
-
幸存者空间的大小与期限阈值相匹配
-
设定一个终点阈值,以尽可能确保只有真正长寿的物体才能使其成为终身
因此,假设您有一个6G堆,您可能会执行类似5G eden + 16M幸存者空间+ 1的持久阈值。
基本过程是
分配到伊甸园
伊甸园填满了
活物被扫入幸存者的空间
来自幸存者空间的活体对象要么被复制到空间要么被提升到终点(取决于时效阈值和可用空间,并且没有时间它们被从1复制到另一个)
在伊甸园留下的任何东西都被扫除了
因此,给定适合您的应用程序的分配配置文件的空间,完全可以配置系统,以便它很好地处理负载。对此有一些警告;
您需要一些长时间运行的测试来正确执行此操作(例如,可能需要数天才能解决CMS碎片问题)
你需要做几次测试才能取得好成绩
你需要在GC配置中一次更改一件事
您需要能够向应用程序提供合理可重复的工作负载,否则很难客观地比较不同测试运行的结果
如果工作量不可预测并且具有巨大的峰值/谷值,那么这将非常难以可靠地完成
积分1-3表示这可能需要很长时间才能正确。另一方面,你可以快速地使它变得足够好,这取决于你是多么肛门!
最后,回应Peter Lawrey的观点,如果你对对象分配非常严格,你可以节省很多麻烦(尽管引入了一些其他的麻烦)。
-
"如果你的主要工作线程正在积极地消耗CPU,那么你最终将使GC线程挨饿"OP说他有一台多核计算机,所以GC线程应该始终能够运行。
-
谢谢你的回答,我使用的是一个相当新的JVM(1.6 u27)。事实上,我正在将物品泄漏到终身一代。
-
@Raedwald忘记了单线程,所以编辑出来了
-
@Thirler根据你的配置和关于200M工作集的评论,你可能会发现只需使用-Xms2048m -Xmx2048m -Xmn1536m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:MaxTenuringThreshold=1 -XX:SurvivorRatio=90 -XX:TargetSurvivorRatio=90 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -verbose:gc就足够了,这意味着2G堆,1.5G伊甸园,8M幸存者空间(伊甸园:幸存者大小是1:90所以每个幸存者都是1 /(90 + 2)的年轻人,占据幸存者空间的90%,在一个物体幸存下来之后进行促进
-
我会尝试这个,我也将安装visualgc,因为jconsole看起来有点难用。为什么当你不希望对象快速使用时,你有-XX:MaxTenuringThreshold = 1?
-
这个想法是你的几代人和这样的空间很可能是任何在年轻收藏中幸存下来的东西都是长寿的物品,因此应该被提升到终身。因此,不要浪费时间在幸存者空间中进行n次乒乓,只需将其投入终身。如果你的尺寸错误,那么这是导致过于频繁的完整GC的缓慢流血。或者,如果您有许多具有适度生命周期的对象,则可以根据需要增加MTT。
-
(续)所以基本上如果你仍然看到流失到Tenured然后你可以删除MTT设置并使用-XX:+PrintTenuringDistribution来确定典型的年龄和MTT的适当值。 kirk.blog-city.com/why_do_i_have_this_long_gc_pause.htm是这个主题的一个很好的实际例子。
-
我知道,随着你的设置,我仍然看到终身一代流血,但它似乎比我现在的情况更接近我需要的东西。我将更新我的问题以反映原因(图形情况经常更新,并且总有一个最后版本可以存活一段时间)。我还必须仔细查看调试输出。我注意到的是,GC的终点阈值决定了一点点反弹(有时是一次,有时是最大值)。
-
良好的出血本身就可以很好,问题是它能够以多快的速度填充,导致一个完整的GC&相对于应用程序的典型生活,这种情况会发生多久。重新MTT,您想要将其修复为(例如)8,然后观察终身分布统计数据。这可以帮助您选择更好的数字。它也可能是你的幸存者空间太小。最后,在收集了分配配置文件的这方面的详细统计信息后,您可能想要开始一个关于幸存者空间调整的新Q.
已经引入稳定Java 1.7的G1GC算法运行良好。您只需指定要在应用程序中使用的最长暂停时间。 JVM将为您处理所有其他事情。
关键参数:
1
| -XX:+UseG1GC -XX:MaxGCPauseMillis=1000 |
还有一些参数需要配置。如果使用4 GB RAM,请将区域大小配置为4 GB / 2048块,大约为2 MB
如果你有8个核心CPU,则微调两个以上的参数
1
| -XX:ParallelGCThreads=4 -XX:ConcGCThreads=2 |
除了这些参数,请将其他参数值保留为默认值
-XX:TargetSurvivorRatio等
有关G1GC的更多详细信息,请查看oracle网站。
设置G1区域的大小。该值为2的幂,范围从1MB到32MB。目标是根据最小Java堆大小拥有大约2048个区域。
1
| -XX:MaxGCPauseMillis=200 |
设置所需最大暂停时间的目标值。默认值为200毫秒。指定的值不适合您的堆大小。
设置STW工作线程的值。将n的值设置为逻辑处理器的数量。 n的值与逻辑处理器的数量相同,最大值为8。
如果有超过八个逻辑处理器,则将n的值设置为逻辑处理器的大约5/8。这在大多数情况下都有效,除了较大的SPARC系统,其中n的值可以是逻辑处理器的大约5/16。
来自oracle的建议:
在评估和微调G1 GC时,请记住以下建议:
年轻代大小:避免使用-Xmn选项或任何或其他相关选项(如-XX:NewRatio)明确设置年轻代大小。 <5233>。
暂停时间目标:评估或调整任何垃圾收集时,始终存在延迟与吞吐量的权衡。 G1 GC是一个增量垃圾收集器,具有统一的暂停,但在应用程序线程上也有更多的开销。 The throughput goal for the G1 GC is 90 percent application time and 10 percent garbage collection time。
最近我用4G堆的G1GC算法取代了CMS,几乎相同的年轻和分组。老一代。我设置了MaxGCPause时间,结果很棒。
我尝试的第一个VM选项是增加NewSize和MaxNewSize并使用其中一个并行GC算法(尝试UseConcMarkSweepGC,它旨在"保持垃圾收集暂停")。
要确认您所看到的暂停是由GC引起的,请打开详细GC记录(-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps)。有关如何阅读这些日志的更多信息,请访问在线。
要了解瓶颈,请在分析器中运行该应用程序。拍摄快照。然后,让应用程序做一会儿。拍摄另一个堆快照。为了查看占用所有空间的内容,请在第二个堆快照之后查找更多内容。 Visual VM可以做到这一点,但也考虑MAT。
或者,考虑使用-XX:+HeapDumpOnOutOfMemoryError以便获得真实问题的快照,而不必在其他环境中重现它。可以使用相同的工具分析保存的堆 - MAT等。
但是,您可能会得到一个OutOfMemoryException,因为您有内存泄漏或者因为您运行的最大堆大小太小。详细的GC日志记录应该可以帮助您回答这两个问题。
-
感谢您的回答。我已经更新了我的问题,说明我了解内存是如何使用的。我知道肯定是免费的,可以收集,但事实并非如此。使用jvisuamvm(和-verbose:gc)我可以看到GC每分钟使用几个小突发,然后最终它将接近最大堆的80%并且不断开始使用高达20%的cpu。
-
只是为了确认,当有OOME时你已经捕获了GC日志和堆转储?考虑发布部分GC日志以及您当前正在进行的分析。
您可以尝试减小新尺寸。这将使它成为更多,更小的集合。但是,它可以使这些短暂的物体进入终身空间。另一方面,您可以尝试增加NewSize,这意味着更少的对象将从年轻一代传出。
然而,我的偏好是创建更少的垃圾,GC将以更一致的方式运行。不要自由创建对象,而是尝试重新使用它们或回收对象。你必须要小心,这不会造成比它的价值更多的麻烦,但你可以减少在某些应用程序中显着创建的垃圾量。我建议使用内存分析器,例如YourKit帮助您识别最大的击球手。
一个极端的例子是创造如此少的垃圾,它不会整天收集(即使是次要收藏)。它可能是服务器端应用程序(但可能不适用于GUI应用程序)
-
谢谢你的回答。我知道创建较少的对象将是一件好事,但短期内不是一个选择。 Jconsole显示'PS old gen'越来越多。理想情况下我认为不应该这样,因为大多数这些元素都可以清理干净并且很安静,我会看看增加伊甸园空间是否有帮助。
-
增加Eden空间可以减少以后需要复制和清理短期对象的更改。