JVM 频繁 FULL GC 快速排查整理
在分享此案例前,先聊聊哪些场景会导致频繁Full GC:
内存泄漏(代码有问题,对象引用没及时释放,导致对象不能及时回收)
死循环
大对象
程序执行了System.gc()
尤其是大对象,80%以上的情况就是他。 那么大对象从哪里来的:
【1】数据库(包括 Mysql和 Mongodb等 NOSql数据库),结果集太大;
【2】第三方接口传输的大对象;
【3】消息队列,消息太大;
根据多年一线互联网经验,绝大部分情况是数据库大结果集导致。好,现在我们开始介绍这次线上故障:
在没有任何发布的情况下,POP(point of purchase的缩写bai,意为“卖点广告”其主要商du业用途是刺激引导zhi消费和活跃dao卖场气氛)服务(接入第三方商家的服务)突然开始疯狂Full GC,观察堆内存监控没内存泄漏,回滚到前一版本,问题仍然存在,尴尬了!!!
按照常规做法,一般先用 jmap导出堆内存快照(jmap -dump:format=b,file=文件名 [pid]),然后用 mat等工具分析出什么对象占用了大量空间,再查看相关引用找到问题代码。为了进一步排查原因,我们在线上开启了 -XX:+HeapDumpBeforeFullGC在其中一台机子上开启了 -XX:HeapDumpBeforeFullGC,总体JVM参数如下:
-Xmx2g
-XX:+HeapDumpBeforeFullGC
-XX:HeapDumpPath=.
-Xloggc:gc.log
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=10
-XX:GCLogFileSize=100m
-XX:HeapDumpOnOutOfMemoryError
注意:JVM 在执行 dump操作的时候是会发生 stop the word事件的,也就是说此时所有的用户线程都会暂停运行。为了在此期间也能对外正常提供服务,建议采用分布式部署,并采用合适的负载均衡算法
dump下来的文件大约 1.8g,用 jvisualvm查看,发现用 char[]类型的数据占用了41%内存,同时另外一个 JdbcSqlStat类型的数据占用了35%的内存,也就是说整个堆中几乎全是这两类数据。如下图:
查看char[]类型数据,发现几乎全是sql语句:
接下来查看char[]的引用情况:并对代码进行修改
这种方式定位问题周期会比较长,如果是关键服务,长时间不能定位解决问题,影响太大。
下面来看看我们的做法。先按照常规做法分析堆内存快照,与此同时另外的同学去查看数据库服务器网络IO监控,如果数据库服务器网络IO有明显上升,并且时间点吻合,基本可以确定是数据库大结果集导致了Full GC,赶紧找DBA快速定位大SQL(对DBA来说很简单,分分钟搞定,如果DBA不知道怎么定位,那他要被开除了,哈哈),定位到 SQL后再定位代码就非常简单了。按照这种办法,我们很快定位了问题。原来是一个接口必传的参数没传进来,也没加校验,导致 SQL语句 where后面少了两个条件,一次查几万条记录出来,真坑啊!这种方法是不是要快很多,哈哈,5分钟搞定。
当时的 DAO层是基于 Mybatis实现的,出问题的SQL语句如下:
<select id="selectOrders" resultType="com.***.Order" >
select * from user where 1=1
<if test=" orderID != null ">and order_id = #{orderID}</if >
<if test="userID !=null">and user_id=#{userID}</if >
<if test="startTime !=null">and create_time >= #{createTime}</if >
<if test="endTime !=null">and create_time <= #{userID}</if >
</select>
上面SQL语句意思是根据 orderID查一个订单,或者根据 userID查一个用户所有的订单,两个参数至少要传一个。但是两个参数都没传,只传了 startTime和 endTime。所以一次 Select就查出了几万条记录。所以我们在使用 Mybatis的时候一定要慎用 if test,一不小心就会带来灾难。后来我们将上面的SQL拆成了两个:
根据订单ID查询订单:
<select id="selectOrderByID" resultType="com.***.Order" >
select * from user where
order_id = #{orderID}
<if test="startTime !=null">and create_time >= #{createTime}</if >
<if test="endTime !=null">and create_time <= #{userID}</if >
</select>
根据 userID查询订单:
<select id="selectOrdersByUserID" resultType="com.***.Order" >
select * from user whereuser_id=#{userID}
<if test="startTime !=null">and create_time >= #{createTime}</if >
<if test="endTime !=null">and create_time <= #{userID}</if >
</select>
----关注公众号,获取更多内容----