现象
线上接口突然出现大量超时,仔细一看,超时的dubbo provider都是一台机器,一看,果然又是full gc耗时过长,最近真和这个干上了,上次是weak reference处理时间过长,这次是新生代晋升担保失败promotion failed。
问题分析
先看gc日志,毕竟这个是最容易拿到的, 对应日志如下:
108817.632: [GC (Allocation Failure) 108817.632: [ParNew (promotion failed): 2357878K->2354600K(2643200K), 0.6570478 secs]
108818.289: [CMS: 2615283K->256960K(2936832K), 4.1329887 secs] 4972611K->256960K(5580032K), [Metaspace: 159337K->159337K(1204224K)], 4.7907632 secs]
[Times: user=3.54 sys=1.31, real=4.79 secs]
从日志可以看出,很明显是新生代promotion failed导致了cms进行了带整理的full gc,即mark-sweep-compact,并且这个过程是串行的,会回收新生代、老年代和metaspace元数据空间,可以看到这个过程花了4.79s,导致接口出现大量超时。
promotion failed的原因
promotion failed的原因一般有以下两个:
内存碎片
顾名思义,晋升担保失败就是指在进行 Young GC 时,Survivor 放不下,对象只能放入 Old,但此时 Old 也放不下。直觉上乍一看这种情况可能会经常发生,但其实因为有 concurrentMarkSweepThread 和担保机制的存在,发生的条件是很苛刻的,除非是短时间将 Old 区的剩余空间迅速填满,例如上文中说的动态年龄判断导致的过早晋升(见下文的增量收集担保失败)。另外还有一种情况就是内存碎片导致的 Promotion Failed,Young GC 以为 Old 有足够的空间,结果到分配时,晋级的大对象找不到连续的空间存放。
增量收集担保失败
分配内存失败后,会判断统计得到的 Young GC 晋升到 Old 的平均大小,以及当前 Young 区已使用的大小也就是最大可能晋升的对象大小,是否大于 Old 区的剩余空间。只要 CMS 的剩余空间比前两者的任意一者大,CMS 就认为晋升还是安全的,反之,则代表不安全,不进行Young GC,直接触发Full GC。
具体原因的确定和解决方式
- 想要确定是否是碎片化导致的,可以添加-XX:PrintFLSStatistics=1的jvm参数,来查看gc时碎片存在的情况。 通过配置 -XX:UseCMSCompactAtFullCollection=true 来控制 Full GC的过程中是否进行空间的整理(默认开启,注意是Full GC,不是普通CMS GC),以及 -XX: CMSFullGCsBeforeCompaction=n 来控制多少次 Full GC 后进行一次压缩。但感觉碎片整理是必须的,这个只是通过参数来减少整理的频次。
- 增量收集担保失败的情况呢,可以看下是否有提前晋升导致old区被快速填满或者其它情况。降低触发 CMS GC 的阈值,即参数 -XX:CMSInitiatingOccupancyFraction 的值,让 CMS GC 尽早执行,以保证有足够的连续空间,也减少 Old 区空间的使用大小,另外需要使用 -XX:+UseCMSInitiatingOccupancyOnly 来配合使用,不然 JVM 仅在第一次使用设定值,后续则自动调整。
网友评论