背景
项目上线之后,出现系统卡顿获取接口无响应,这时查看服务器的各项指标监控,能看到JVM中有像下图类似的FGC的情况 ,但是对于后续怎么去解决,有些同学可能没有清晰的思路。
这篇博客主要就通过分享一个具体的例子,给大家提供分析定位解决问题的一个思路
问题定位
添加JVM参数
-XX:+HeapDumpOnOutOfMemoryError //oom时自动dump
-XX:+HeapDumpBeforeFullGC //fgc前自动dump
-XX:+HeapDumpAfterFullGC //fgc后自动dump
-XX:HeapDumpPath=/opt/dump //指定dump文件生成路径
分析dump文件
通过MAT下载地址 根据JDK版本选择MAT版本工具分析FGC 前后的dump文件可以比较容易的帮我们找到出问题的地方,下面是油卡dump文件分析的例子
FGC前
FGC后
分析报告的第一个问题有明显的区别,点开GC前suspcet1的detail
可以看到树中累积对象大小,其中com.mysql.jdbc.JDBC42ResultSet中积累了大量的com.mysql.jdbc.ByteArrayRow,我们选中com.mysql.jdbc.JDBC42ResultSet -> List Objects -> with outgoing references 可以看到对象中的详细信息
其中能看到SQL:
SELECT d.is_deleted FROM oil_write_off_xxxxx d, oil_request_funds_trading t WHERE t.request_funds_serial_no = 'Q001548592'
返回了三十六万多条数据,到这里就基本定位了FGC的原因
问题解决
定位SQL
思路1:在项目中搜索
SELECT d.is_deleted FROM oil_write_off_xxxxx d, oil_request_funds_trading t WHERE t.request_funds_serial_no = 'Q001548592'
这个SQL的相关代码,结果相关的SQL只有一条更新的语句
思路2:由于OOM是SQL引起的,那应该能关联到具体的请求,可以去Pinpoint上找到对应的请求查看调用链。根据OOM的时间去Pinpoint上查询相关的请求,时间在9:40-9:50之间
在9:40-9:50中间有一个错误请求,我们直接点进去看调用链
可以看到在更新语句之后执行了一个 selectBySql(String sql) 的方法,这个方法里面的SQL正好就是我们引起OOM的SQL,这个SQL作用是将之前的数据记录下来作为日志变化前数据,然后在执行update语句,代码如下:
// 省略
} else {
// 获取查询老值的sql
String selectSql = getSelectSQL(sqlInfo);
ActionLogService actionLogService = SpringContext.getApplicationContext().getBean(ActionLogService.class);
List<Map<String, String>> oldParamList = actionLogService.selectBySql(selectSql);
originalValue = DEFAULT_OBJECT_MAPPER.writeValueAsString(oldParamList);
// 执行业务操作
}
// 省略
由于update语句中使用了inner join 关联了一张辅表,where查询条件后面只有主表的条件,所以在生成的select语句中,辅表的数据会全都被拉出来。
解决
在SQL解析中增加对update语句中join的判断,如果包含join,则不去执行查询更新前数据的逻辑
评论区