现象
服务运行一段时候,无法正常接收请求和响应消息。重启后系统正常运行。隔了几天后,再次出现系统假死,无法响应请求的情况。
排查
1.利用jstat命令查看gc情况
JVM几乎每秒就要进行1到2次fullGC,这么夸张的GC频率,服务直接进入假死状态,无法正常运作。那么,是什么造成立这么频繁的fullGC呢?
2.jmap导出dump文件,利用jvisualvm进行进一步分析
可以看到,char[]类型的实例占据了接近700M的内存,并且存在160多万个实例,而我们的服务器配置,是2核4G,分配给JVM的只有2G,年轻代和老年代各1G。
查看具体的实例内容,按大小排序,排名前面几个是一个MySQL的insert语句,但是看它的大小,几乎个个都有100多M。而其中的100多万个实例中,类似以下图片中的实例占据了绝大部分。而这些,就是sql语句中的参数占位符。
到了这一步,其实心里已经差不多对问题的原因有了一个差不多的猜测。估计就是因为程序执行了一个批量插入的操作,并且这个批量插入涉及的量实在太多了,估计有几十万的数量,结果出现了问题。
根据sql语句的详细内容,我们很轻易就能定位到程序中的问题代码。具体代码涉及公司项目,就不同贴出来的。简单来说,就是使用了最常见的myBatis批量插入的实现方式,xml中的sql如下图所示。但是就是一次插入的数量实在太多,有十几,二十多万条。
<insert id="batchSave" parameterType="java.util.List">
insert into computer_box_calc_match (
diskless_room_id,
box_id,
calc_id,
version,
add_time
)
values
<foreach collection="list" item="item" index="index" separator="," >
(#{item.disklessRoomId}, #{item.boxId}, #{item.calcId}, #{item.version}, #{item.addTime})
</foreach>
</insert>
数量巨大的插入项,导致生成了巨大的sql语句以及数量众多的占位符对象。同时因为SQL过于巨大,MyBatis对SQL的解析也需要极长的时间,同时也占用了大量的CPU。这样就导致了其他请求的响应时间也变长,堆内存中的对象逐渐累积,导致了fullGC的发生,但fullGC在进一步抢占CPU的同时,又不能有效回收垃圾释放空间,导致频繁FullGC,系统彻底卡死。
解决方案
1.对于批量的插入进行一定的拆分,将一次insert语句的插入数量控制在100左右,保证单次插入的时间较短,垃圾回收也能够顺利回收垃圾。
2.对机器的配置做一个升级,内存加到8G,分配给JVM 6G,年轻代和老年代各占3G。