某方法,在50用户并发时,执行4分钟,TPS下降,堆内存的老年代占用100%,并频繁执行fullGC,单个实例占用堆内存94%。
该问题的根本原因:
频繁FULL GC 是项目使用的mysql jdbc driver版本低(5.1.5),这个版本有bug. https://bugs.mysql.com/bug.php?id=36565 .
解决办法:
- 方法一 (推荐): 升级mysql jdbc driver 到更新的版本(5.1.11以上),比如5.1.18, 或
- 方法二:修改mybatis的配置文件,不设置queryTimeout时间,比如,从jd-treasure-dao项目中的mybatis-config.xml文件中去掉下面的设置
<!-- 设置超时时间,它决定驱动等待一个数据库响应的时间 -->
<setting name="defaultStatementTimeout" value="600"/>
其他有些业务线也使用了5.1.5版本,但因为没有设置defaultStatementTimeout,所以没有出现该问题。
分析过程:
1.通过dump文件,看到有大量的CancelTask 对象(928606个),应该是GC不掉
- 查看driver 5.1.5 的源代码 StatementImpl.java
public boolean execute(String sql) throws SQLException {
…
if (locallyScopedConn.getEnableQueryTimeouts() &&
this.timeoutInMillis != 0
&& locallyScopedConn.versionMeetsMinimum(5, 0, 0)) {
timeoutTask = new CancelTask(this);
ConnectionImpl.getCancelTimer().schedule(timeoutTask,
this.timeoutInMillis);
}
…
}
该类里还有executeQuery等几个方法都使用到了CancelTask. 其中CancelTimer是ConnectionImpl里的一个静态变量,不会被GCed. 造成每次进行SQL操作新生成的cancelTask对象不会被GCed. timeoutInMillis 在一元夺宝项目里是通过mybatis-config.xml文件的defaultStatementTimeout设置的。