GC调整 – 阻止完整GC

我正在尝试避免在生产中运行Tomcat中的Grails应用程序的Full GC(来自下面的gc.log示例)。 有关如何更好地配置GC的任何建议?

14359.317:[Full GC 14359.317:[CMS:3453285K-> 3099828K(4194304K),13.1778420 secs] 4506618K-> 3099828K(6081792K),[CMS Perm:261951K-> 181304K(264372K)] icms_dc = 0,13.1786310 secs] [次:user = 13.15 sys = 0.04,real = 13.18 secs]

我的VM参数如下:
-Xms = 6G
-Xmx = 6G
-XX:MaxPermSize参数= 1G
-XX:新尺寸= 2G
-XX:MaxTenuringThreshold = 8
-XX:SurvivorRatio = 7
-XX:+ UseConcMarkSweepGC
-XX:+ CMSClassUnloadingEnabled
-XX:+ CMSPermGenSweepingEnabled
-XX:+ CMSIncrementalMode
-XX:CMSInitiatingOccupancyFraction = 60
-XX:+ UseCMSInitiatingOccupancyOnly
-XX:+ HeapDumpOnOutOfMemoryError
-XX:+ PrintGCDetails
-XX:+ PrintGCTimeStamps
-XX:+ PrintTenuringDistribution
通过-Dsun.reflect.inflationThreshold = 0

     14169.764:[GC 14169.764:[ParNew所需幸存者大小107347968字节,新阈值8(最大8) - 年龄1:15584312字节,15584312总计 - 年龄2:20053704字节,35638016总计 - 年龄3:13624872字节,49262888总计 - 年龄4:14469608字节,63732496总计 - 年龄5:10553288字节,74285784总计 - 年龄6:11797648字节,86083432总计 - 年龄7:12591328字节,98674760总计:1826161K-> 130133K(1887488K),0.1726640秒] 5216326K-> 3537160K (6081792K)icms_dc = 0,0.1733010 secs] [次:用户= 0.66 sys = 0.03,实际= 0.17秒] 14218.712:[GC 14218.712:[ParNew所需幸存者大小107347968字节,新阈值8(最大8) - 年龄1: 25898512字节,25898512总共 - 年龄2:10308160字节,36206672总数 - 年龄3:16927792字节,53134464总数 - 年龄4:13493608字节,66628072总数 - 年龄5:14301832字节,80929904总计 - 年龄6:10448408字节,91378312总计 - 年龄7:11724056字节,103102368总计 - 年龄8:12299528字节,115401896总数:1807957K-> 147911K(1887488K),0.1664510秒] 5214984K-  > 3554938K(6081792K)icms_dc = 0,0.1671290 secs] [次:用户= 0.61 sys = 0.00,实际= 0.17秒] 14251.429:[GC 14251.430:[ParNew所需幸存者大小107347968字节,新阈值7(最大8) - 年龄1:25749296字节,25749296总计 - 年龄2:20111888字节,45861184总计 - 年龄3:7580776字节,53441960总计 - 年龄4:16819072字节,70261032总计 - 年龄5:13209968字节,83471000总计 - 年龄6:14088856字节, 97559856总共 - 年龄7:10371160字节,107931016总数 - 年龄8:11426712字节,119357728总数:1825735K-> 155304K(1887488K),0.1888880秒] 5232762K-> 3574222K(6081792K)icms_dc = 0,0.1895340 secs] [次:用户= 0.74 sys = 0.06,real = 0.19 secs] 14291.342:[GC 14291.343:[ParNew所需幸存者大小107347968字节,新阈值7(最大8) - 年龄1:25786480字节,25786480总计 - 年龄2:21991848字节,47778328总计 - 年龄3:16650000字节,64428328总计 - 年龄4:7387368字节,71815696总计 - 年龄5:16777584字节,88593280总计 - 年龄6:13098856字节,101692136总计 - 年龄  7:14029704字节,115721840总计:1833128K-> 151603K(1887488K),0.1941170秒] 5252046K-> 3591384K(6081792K)icms_dc = 0,0.1947390秒] [时间:用户= 0.82 sys = 0.04,实际= 0.20秒] 14334.142: [GC 14334.143:[ParNew所需幸存者大小107347968字节,新阈值6(最大8) - 年龄1:31541800字节,31541800总计 - 年龄2:20826888字节,52368688总计 - 年龄3:19155264字节,71523952总计 - 年龄4: 16422240字节,87946192总共 - 年龄5:7235616字节,95181808总数 - 年龄6:16549000字节,111730808总数 - 年龄7:13026064字节,124756872总数:1829427K-> 167467K(1887488K),0.1890190秒] 5269208K-> 3620753K(6081792K )icms_dc = 0,0.1896630 secs] [次:用户= 0.80 sys = 0.03,real = 0.19 secs] 14359.317:[Full GC 14359.317:[CMS:3453285K-> 3099828K(4194304K),13.1778420 secs] 4506618K-> 3099828K(6081792K ),[CMS Perm:261951K-> 181304K(264372K)] icms_dc = 0,13.1786310 secs] [次:用户= 13.15 sys = 0.04,real = 13.18 secs] 14373.287:[GC [1 CMS-initial-mark:3099828K( 4194304K)] 310009  4K(6081792K),0.0107380秒] [时间:用户= 0.01 sys = 0.00,实际= 0.00秒] 14373.298:[CMS-concurrent-mark-start] 14472.579:[GC 14472.579:[ParNew所需幸存者大小107347968字节,新阈值8(最多8) - 年龄1:42849392字节,42849392总数:1677824K-> 86719K(1887488K),0.1056680秒] 4777652K-> 3186547K(6081792K)icms_dc = 0,0.1063280秒] [时间:用户= 0.61 sys = 0.00,实际值= 0.11秒] 14506.980:[GC 14506.980:[ParNew所需幸存者大小107347968字节,新阈值8(最大8) - 年龄1:42002904字节,42002904总计 - 年龄2:35733928字节,77736832总计:1764543K-> 96136K( 1887488K),0.0982790秒] 4864371K-> 3195964K(6081792K)icms_dc = 0,0.0988960秒] [次:用户= 0.53 sys = 0.01,实际= 0.10秒] 14544.285:[GC 14544.286:[ParNew所需幸存者大小107347968字节,新阈值8(最大8) - 年龄1:26159736字节,26159736总计 - 年龄2:37842840字节,64002576总计 - 年龄3:33192784字节,97195360总计:1773960K-> 130799K(1887488K),0.1208590秒] 4873788K-> 32306  28K(6081792K)icms_dc = 0,0.1215900秒] [时间:用户= 0.59 sys = 0.02,实际= 0.13秒] 14589.266:[GC 14589.266:[ParNew所需幸存者大小107347968字节,新阈值4(最大8) -  1岁:28010360字节,28010360总计 - 年龄2:21136704字节,49147064总计 - 年龄3:35081376字节,84228440总计 - 年龄4:32468056字节,116696496总数:1808623K-> 148284K(1887488K),0.1423150秒] 4908452K-> 3248112K( 6081792K)icms_dc = 0,0.1429440 secs] [次:用户= 0.70 sys = 0.02,实际= 0.14秒] 14630.947:[GC 14630.947:[ParNew所需幸存者大小107347968字节,新阈值8(最大8) - 年龄1:28248240字节,28248240总共 - 年龄2:20712320字节,48960560总数 - 年龄3:18217168字节,67177728总数 - 年龄4:34834832字节,102012560总数:1826108K-> 140347K(1887488K),0.1784680秒] 4925936K-> 3275469K(6081792K) icms_dc = 0,0.1790920 secs] [次:用户= 0.98 sys = 0.03,实际= 0.18秒] 14664.779:[GC 14664.779:[ParNew所需幸存者大小107347968字节,新阈值5(最大8) - 年龄  1:25841000字节,25841000总计 - 年龄2:22264960字节,48105960总计 - 年龄3:17730104字节,65836064总计 - 年龄4:17988048字节,83824112总计 - 年龄5:34739384字节,118563496总计:1818171K-> 147603K(1887488K) ),0.1714160秒] 4953293K-> 3282725K(6081792K)icms_dc = 0,0.1720530秒] [时间:用户= 0.82 sys = 0.11,实际= 0.17秒] 14702.488:[GC 14702.489:[ParNew所需幸存者大小107347968字节,新阈值8(最多8) - 年龄1:26887368字节,26887368总计 - 年龄2:21403352字节,48290720总计 - 年龄3:18732224字节,67022944总计 - 年龄4:17640576字节,84663520总计 - 年龄5:17942952字节,102606472总计:1825427K-> 142695K(1887488K),0.2118320 secs] 4960549K-> 3312168K(6081792K)icms_dc = 0,0.2124630 secs] [次:用户= 1.13 sys = 0.14,real = 0.21 secs] 

我的目标是:我想限制Tenured的最小值,我正在服务请求并期望超过一定数量的共享对象,每个其他对象仅对手头的请求有用。 因此,通过使用大的NewSize和增加的TenuringThreshold,并希望没有这些单个服务对象。

以下是支持我的策略:
-Xms = 6G
-Xmx = 6G
-XX:NewSize = 2G //大空间,以便ParNew不经常出现,让对象有时间到期
-XX:MaxTenuringThreshold = 8 //更多地限制任期
-XX:SurvivorRatio = 7 //基于示例-XX:CMSInitiatingOccupancyFraction = 60
//阻止促销分配导致的完整GC失败
-XX:+ UseCMSInitiatingOccupancyOnly
//根据示例使用上面的那个

MaxPermSize = 1G和“-Dsun.reflect.inflationThreshold = 0”与另一个我宁愿分开的问题有关。

“-XX:+ CMSClassUnloadingEnabled”和“-XX:+ CMSPermGenSweepingEnabled”是因为grails严重依赖和额外的类关闭和reflection

-XX:+ CMSIncrementalMode是一个没有取得多大成功的实验

发布的日志片段显示您有大量的对象,这些对象的生存时间超过320秒(每个年轻集合大约40秒,对象在促销前通过8个集合生存)。 剩下的物体然后流入终身,最终你击中了一个显然意外的完整gc,实际上并没有收集太多。

3453285K->3099828K(4194304K)

也就是说,当你被触发时,你有一个4G天赋,它是~82%满(3453285/4194304),在13个长时间后你已经满了74%。

这意味着需要13秒来收集~350M的总计,在6G堆的情况下并不是很多。

这基本上意味着你的堆不够大,或者更可能是你有内存泄漏。 这样的泄漏对于CMS来说是一件可怕的事情,因为并发终身收集是一个非压缩事件,这意味着终身是一个免费列表的集合,这意味着碎片可能是CMS的一个大问题,这意味着您对终身的利用变得越来越低效,意味着促销失败事件的可能性增加(尽管如果这是一个事件,那么我希望看到一条日志消息说明),因为它想要推广(或认为它需要推广)X MB到终身但它没有(连续的)空闲列表> = X MB可用。 这会触发意外的终极收集,这是一个非远程并发的STW事件。 如果你实际上有很少收集(就像你那样),那么毫不奇怪你坐在你的拇指上。

一些一般性的指示,在很大程度上重申弗拉基米尔·西特尼托夫所说的……

  • 在多核盒子上使用iCMS是没有意义的(除非你有很多 JVM或其他进程在那个盒子上运行,以至于JVM确实缺少CPU)因此删除了这个开关
  • 你的年轻系列不必要地长,因为在每个系列的幸存者空间之间复制相对大量的记忆,150-200ms是一个非常庞大的ParNew系列
    • 对年轻人问题的正确答案取决于分配行为的真正含义(例如,你可能会更好地提前做好准备,减少分散对终身收入的影响,或者你可能会更好地拥有更大规模的新产品。 gen和减少年轻的gen集合的频率,以便促进更少的对象,以便最小化流失到tenured)。

一些问题…

  • 它最终是OoM还是恢复?
  • 在此日志片段中,应用程序是否处于稳定状态(在启动之后的某个时刻受到一致的负载)或者是否处于压力之下?

我正在服务请求并期望超过一定数量的共享对象,每个其他对象仅对手头的请求有用。 这就是理论,但任何类型的缓存都很容易使该假设无效,并创建超出请求的对象。

正如其他人所指出的那样,你的巨大年轻一代和延长的任期似乎都不起作用。

您应该分析您的应用程序并分析对象的年龄分布。 我非常确定Grails会缓存超出请求范围的各种事情,这就是泄漏到旧版本的内容。

你真正想要的是牺牲年轻一代的暂停时间(对于年轻的2GB)来推迟不可避免的 – 一个6GB的旧版本。 这并不是你在那里做的一个很好的权衡。

相反,你可能应该瞄准更好的年轻gen暂停时间,并允许CMS燃烧更多的CPU时间:更多的相位GC线程(不记得那个选项),更高的GCTimeRatioMaxGCPauseMillis > MaxGCMinorPauseMillis来承受压力次要collections并允许他们达到暂停目标,而不必resize以适应主要collections限制。

为了减少主要GC的痛苦,您可能需要阅读: http ://blog.ragozin.info/2012/03/secret-hotspot-option-improving-gc.html(此补丁应该在j7u4中)。 CMSParallelRemarkEnabled应该被启用,不确定这是否是默认值。

替代方案:使用G1GC

就个人而言,我有一些可怕的经历,由于一些非常大的类似LRU的工作负载,G1GC正在进入一个角落,然后回到一个大的,世界各地的集合,远远超过CMS在同一工作负载下遇到的并发模式故障。

但是对于其他工作负载(比如你的工作负载),它实际上可以完成工作并逐步收集旧代,同时还可以压缩并避免任何大的暂停。

如果你还没有试一试。 再次,在你这样做之前更新到最新的java7,G1仍然有一些问题,他们试图解决它的启发式问题。

编辑:自从我写完这个答案以来,Oracle已经改进了G1GC的启发式和一些瓶颈。 现在绝对值得一试。

另一种选择:吞吐量收集器

因为你已经为2GB的年轻人使用并行收集器并且在200ms的暂停时间内逃脱了…为什么不在你的6G堆上尝试并行的旧收发器呢? 它可能需要少于你用CMS看到的10s +主要collections品。 每当CMS遇到其失败模式之一时,它就会执行单线程 ,世界各地的集合。

请描述Tomcat可以使用多少CPU? 4?

你用的是什么java版本? (> 1.6.0u23?)

0)从Full GC输出,它看起来肯定你正在达到内存限制:即使在完全gc之后,仍有3099828K的已用内存(4194304K中)。 当你内存不足时,没有办法阻止Full GC。

您的应用需要3.1Gb工作集吗? 那是3.1Gb的非垃圾内存!

如果这是预期的,则是时候增加-Xmx / -Xms。 否则,是时候收集和分析堆转储以识别内存耗尽。

在解决了3Gb工作集的问题后,您可能会发现以下建议很有用:从我的角度来看,常规(非增量)CMS模式和减少NewSize值得尝试。

1)当CMS线程将CPU产生到其他线程时,增量模式针对单个cpu机器。

如果您有一些备用CPU(例如,您正在运行多核计算机),最好在后台执行GC而不会产生结果。

因此,我建议删除-XX:+ CMSIncrementalMode。

2)-XX:CMSInitiatingOccupancyFraction = 60告诉CMS在OLD gen已满60%后启动后台GC。

如果堆中存在垃圾,并且CMS无法跟上它,那么降低CMSInitiatingOccupancyFraction是有意义的。 例如,-XX:CMSInitiatingOccupancyFraction = 30,因此当旧的gen为30%时,CMS将开始并发收集。 目前很难判断是否是这种情况,因为你在堆中没有垃圾。

3)看起来“延长使用期限”没有帮助 – 即使在7-8次任期之后,物体也不会消失。 我建议减少SurvivorRatio(例如,SurvivorRatio = 2,或者只是删除选项并坚持默认)。 这将减少使用期限,从而减少次要gc暂停。

4)-XX:NewSize = 2G。 你为NewSize尝试了较低的值吗? 说,NewSize = 512m。 这应该可以减少次要的gc暂停,并使促销变得年轻 – >不那么大,简化了CMS的工作。

你的幸存者数量并没有减少很多,如果有的话 – 理想情况下它们应该急剧下降,因为你只想让少数物体存活足够长的时间才能到达老一代。

这表明许多对象的生存时间相对较长 – 例如,当您有许多打开的连接,线程等不能快速处理时,就会发生这种情况。

(顺便提一下,您是否有任何选项可以更改应用程序,或者您只能修改GC设置?可能还有Tomcat设置会产生影响……)

下面是我对4核Linux盒的设置。

根据我的经验,您可以调整-XX:NewSize -XX:MaxNewSize -XX:GCTimeRatio以实现高吞吐量和低延迟。

-服务器
-Xms2048m
-Xmx2048m
-Dsun.rmi.dgc.client.gcInterval = 86400000
-Dsun.rmi.dgc.server.gcInterval = 86400000
-XX:+ AggressiveOpts
-XX:GCTimeRatio = 20
-XX:+ UseParNewGC
-XX:ParallelGCThreads = 4
-XX:+ CMSParallelRemarkEnabled
-XX:ParallelCMSThreads = 2
-XX:+ CMSScavengeBeforeRemark
-XX:+ UseConcMarkSweepGC
-XX:+ UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction = 50
-XX:新尺寸=512米
-XX:MaxNewSize =512米
-XX:PermSize =256米
-XX:MaxPermSize参数=256米
-XX:SurvivorRatio = 90
-XX:TargetSurvivorRatio = 90
-XX:MaxTenuringThreshold = 15
-XX:MaxGCMinorPauseMillis = 1
-XX:MaxGCPauseMillis = 5
-XX:+ PrintGCDateStamps
-XX:+ PrintGCDetails
-XX:+ PrintTenuringDistribution
-Xloggc:./日志/ gc.log