最近项目上遇到了OOM问题,如下图:
一、背景:项目上需要同步大量的老数据到新建的表中,持续比较长的时间使用common-dbcp连接池执行过程中出现了上述OOM问题,导致FULLGC一直卡死,CPU使用率居高不下
二、排查过程:
1、先查看应用进程号: ps -ef | grep 应用名 ,也就是 pid
2、查看pid垃圾回收情况: jstat -gc pid 5000(时间间隔)
3、dump jvm二进制的内存详细使用情况: jmap -dump:format=b,file=/home/test/oom.hprof pid
4、使用 Memory Analyzer (MAT)来分析,简称MAT,分析内存泄露对象
注意: 其中 pid 是指程序进程号
通过MAT分析,得出结论:主要是由于大量的JDBC42PreparedStatement对象没有被回收引起的,即 openStatements 中引用的对象
三、分析原因
通过源码我们得知,清除 openStatements 中 JDBC42PreparedStatement对象需要两个条件:
1、connection close会清除,源码如下:
2、statement close,也会清除 openStatements 中 JDBC42PreparedStatement 对象,源码如下:
而我们知道,common-dbcp连接池不会直接关闭 connection 和 statement ,而是直接将 connection 和 statement 返回给连接池中,源码如下:
四、为什么设置了 PoolPreparedStatements 为 true时会引发这个问题
我们知道 PoolPreparedStatements 默认为 false,源码:
当设置为 true时,将会创建 GenericKeyedObjectPoolFactory ,从而创建出 PoolingConnection(common-dbcp中的类),如果是 false 时,将会创建 JDBC4Connection (mysql-connector中的类),源码如下:
当执行完sql语句调用 Statement 的 close 方法时, PoolPreparedStatements 为false时,调用的是 JDBC42PreparedStatement 的close方法, PoolPreparedStatements 为 true 时,调用的是 PoolablePreparedStatement 的 close 方法,下面结合源码和debug方法来分别说明:
1、PoolPreparedStatements 为false时,调用的是 JDBC42PreparedStatement 的close方法过程如下:
结论:上述过程最后会清除 OpenStatements
2、PoolPreparedStatements 为 true 时,调用的是 PoolablePreparedStatement 的 close 方法过程如下:
结论:上述过程只是将 connection 返回给连接池(pool)中而已,因此不会清除 OpenStatements
五、多思考一下,PoolPreparedStatements 设置为 true 时,有没有在什么情况下连接池也会清除 OpenStatements 的
答案是有的,当数据库连接池中的 connection 数量大于等于pool中允许的空闲的最大数量就会关闭 connection,从而清除 OpenStatements,源码如下: