jvm allocation failure_Segmentation fault (core dumped)

(36) 2024-06-15 10:01:01

一、现象

某系统每台机器每天都会出现一次fgc时间过长的告警。

 

二、分析

1.查看监控,发现每天无规律的会发生达到5-7秒的fgc。

 

jvm allocation failure_Segmentation fault (core dumped) (https://mushiming.com/)  第1张

注:该监控上的fullgc监控采用的是jmx的统计方式,所以其实是对oldgc的监控,因为除了CMS和 G1以外的垃圾回收器的old gc只能由fullgc触发,所以大部分情况下oldgc次数就是fullgc次数, oldgc时间接近于fullgc时间。 

 

2.查看对应fgc时间点的gc日志,发现全部都发生了promotion fail,确认是晋升失败引起过长的fgc(Old Serial gc)

 

jvm allocation failure_Segmentation fault (core dumped) (https://mushiming.com/)  第2张

 

从gc日志可知,可用年轻代K,发生promotion fail的时候年轻代几乎没有回收对象(从K到K只减少了4064K),年轻代剩余可用空间K-K=K<K,年轻代空间不足,需要提前晋升到年老代K-K=K。年老代此时剩余可用空间K-K=K,小于K,年老代不足,晋升失败,触发fgc。

 

注:promotion fail(晋升失败) eden区空间不足引起ygc,ygc后eden和survivor区回收不了的对象,需要复制到另外一个survivor区, 这个时候发现这个survivor区空间不足以容纳这些对象,发生提前晋升到老年代,老年代也容纳不了这些 对象,从而触发了fgc(CMS不提供fgc方案,所以使用的是串行fgc,效率可见而知。G1垃圾回收器也是如此) 产生promotion fail的可能原因: 1.年轻代太小,需要调大年轻代 2.年老代太小,需要调大年老代 3.年老代空间充足,但有内存碎片,需要开启内存碎片整理

三、解决过程

分析JVM参数

-server -Xms8192m -Xmx8192m -XX:MaxMetaspaceSize=256m 

可以看出,原来的JVM参数除了设置堆大小和方法区大小,其它的配置都是使用默认值,即各个分区大情况(整堆大小:8192m):

年轻代大小:8192m*1/3=K

年老代大小:8192m*2/3=K

年轻代eden区大小: K*8/10 = K

年轻代survivor区大小:K*2/10=K

可用年轻代大小:eden区+单个surviror区=K+K=K

 

排除是由于内存碎片而导致的promotion fail

1、默认已经开启内存碎片整理:-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0

2、从名字服务/sunfire监控上看内存情况也可以知道old gc都发生在使用率达到90%,也符合-XX:CMSInitiatingOccupancyFraction默认值是90的配置。

 

第一次调优

考虑到业务是对App提供服务,理论上很多对象都是短生命对象,因而思路一是加大年轻代,提供更大的ygc空间,让对象尽可能在年轻代回收,避免对象晋升到老年代。

 

jvm参数调整

    -server -Xms8192m -Xmx8192m -Xmn4g -XX:MaxMetaspaceSize=256m

加大年轻代空间到4g,有可能加快old gc频率,所以加大年轻代的前提是老年代原来的old gc频率不是很高:如下图,>2小时一次oldgc)

 

jvm allocation failure_Segmentation fault (core dumped) (https://mushiming.com/)  第3张

效果

ygc每分钟减少2~5次,但每天仍然会有1到2次的promotion fail发生。

 

jvm allocation failure_Segmentation fault (core dumped) (https://mushiming.com/)  第4张

 

结论

第一次调优失败。

 

第二次调优

分析第一次调优后的gc日志发现,发生promotion fail的时候年轻代的空间几乎没被回收,怀疑会不会有大对象瞬间堆积(从监控上观察内存使用情况,排除了内存溢出的可能)。因而希望在发生fgc的时候能知道当时内存的使用情况。

 

 

jvm参数调整:

-server -Xms8192m -Xmx8192m -Xmn4g -HeapDumpBeforeFullGC -XX:MaxMetaspaceSize=256m

在即将发生fgc之前dump内存,dump文件没指定路径则存放在当前目录。

 

结果:

11.17号02:27分dump出了一份fgc的内存快照:

http://xxx.xxx.cn/service/index.html#/zprofiler/heap/upload-20181117023059185-zprofiler-heap.675B.bin/overview

 

分析:

借助zprofile分析,确实没有可疑的内存溢出。分析类簇,发现ConcurrentSkipListMap$Node和ConcurrentHashMap$Node各占了1.2g和1.8g。继续跟踪,发现很多CacheStatState和DBStatState,这个是框架里的缓存和数据库统计对象,会在内存堆积60秒后再写入磁盘:

 

jvm allocation failure_Segmentation fault (core dumped) (https://mushiming.com/)  第5张

 

jvm allocation failure_Segmentation fault (core dumped) (https://mushiming.com/)  第6张

 

 

jvm allocation failure_Segmentation fault (core dumped) (https://mushiming.com/)  第7张

 

基于上面的分析,业务上缓存和数据库查询使用得很多(无法短时间内减少统计对象),框架也不再提供更新(无法短时间内调整统计对象的刷盘策略),所以只能继续尝试第二次JVM参数调优。

统计对象堆积可能会占用比较大的内存,但这些对象是短生命周期的,很多用完就应该回收。所以第二次参数调优仍然坚持第一次的调优方向:让对象尽可能在年轻代消亡。

这次加大了年轻代的扩容力度:

1.年轻代加大到5g

2.提高晋升年龄(ajdk默认值是6,提高到最大值15)

3.加大survivor的空间使用率(ajdk默认值是50,提高到90%)

调整后的JVM参数

-server -Xms8192m -Xmx8192m -Xmn5g -XX:MaxMetaspaceSize=256m -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=15

 

效果

promotion fail从每天1-2次降低到0-1次,但是每次fgc停顿时间增加到了6-7秒。主要是调大的年轻代加大了old gc在重新标记阶段的扫描停顿时间。

结论

第二次调优失败。

 

第三次调优

在对前面两次的gc日志分析后发现,年轻代无论调多大,总会有对象在某瞬间堆积导致promotion fail,配置多大吃多大,所以加大年轻代不仅没有解决问题,反而增加了fgc时间(cms两次停顿都需要扫描年轻代)。

 

因此调整了调优方向:

在无法确定对象为何堆积的情况下,把fgc的时间打散,即用多次cms gc来避免fgc,减少集中停顿的时间。

需要注意的是,因为cms gc是并发gc,所以会跟用户线程抢占cpu资源。因为我们的uae容器的cpu是 8核,负载较低(1~1.5左右),cpu使用率也在1~1.5之间。cpu不是瓶颈,CMS并发对cpu资源影响不 大。 

这次调优从promotion fail的产生原因,在原来参数的基础上,得出理论值公式:eden+survivor<=old*(1-阀值),计算出阀值60,为加大减少promotion fail的可能,设置为55。

调整后的JVM参数

-XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=15 -XX:CMSInitiatingOccupancyFraction=55 -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpBeforeFullGC

 

效果

灰度一台机子,两天没有发生promotion fail,也没有发生full gc,每次cms gc时间为300到500ms。灰度扩大到所有机器,在12.8凌晨第一轮压测没有发生promotion fail,也没有发生fgc。

 

结论

fgc从每天1~3次减少到现在每天0次,停顿时间从每次5~7s减少到现在每次300~700ms,fgc时间过长的问题基本得到解决。问题虽然解决但仍还有可优化空间。

 

 

后续可调优空间

1、升级为G1垃圾回收器

  • ‌ 更高的内存使用率

(1)相对于CMS在多次gc后才进行内存压缩,G1在每次回收时都会进行“局部压缩”(把多个region合并为一个region)。

(2)G1垃圾回收器的eden、survivor、old区只是逻辑上的概念,物理上并没有明显的区分,一个region属于哪个分区是不确定的,这就使得G1可以通过灵活调整分区大小来达到用户设定的停顿时间。这点特别符合我们的问题场景:大部分时间对象都能在ygc时候在年轻代里消亡,只有小部分时间对象在年轻代堆积发生promotion fail。如果使用G1,大部分时间下年轻代region会占大部分,在发生对象堆积的时候,可能会动态减少年轻代region数而提高老年代region数,避免promotion fail而出现过长的fgc。

  • 可设置的停顿时间,配置简单。

  • 业务服务器8核cpu、8g的JVM内存,符合G1的硬件配置要求。

2、优化程序,根究对象在年轻代瞬间堆积的原因

虽然通过dump文件发现统计缓存对象存在大量堆积的情况,但从监控上看堆积的时间点没有明显的流量异常,需要进一步确认引起promotion fail的对象,才可能对症下药优化程序。

 

THE END

发表回复