日期:2014-05-16  浏览次数:20466 次

大量JDBC批处理内存溢出问题分析

2012年1月9日,去客户现场解决性能问题。

?

背景:客户需要数据从一个数据库到另一个数据库转移。本来可以使用ETL工具解决,但是由于一些客观原因又不能使用ETL系统,因此项目组自己做了一个数据迁移功能。除了业务部分的数据组织外,其他功能非常简单,就是利用JDBC组织数据,然后批量提交。

?

问题:数据量在19万条,内存控制在1G左右,在8万条左右,内存溢出。监控JVM,发现Old区域和eden区域都满了,GC很吃力,效果不好。因此定位内存泄漏。

?

代码分析:

原有代码使用hibernate分页获取数据,每次5000条,然后进入子程序,创建PreparedStatement,批量组织sql,提交,之后释放PreparedStatement和connection。规范都可以,最后释放也在finally块中。

?

第一步怀疑,hibernate的级联查询有问题,关闭jdbc部分,只查询。JVM监控没有问题,19万很快结束。

?

第二步怀疑,其他部分影响,通过堆dump分析,发现DB2的JDBC驱动类比较多。

?

第三步分析jvm监控,发现系统分页每次5000条执行结束后,内存跳一大块,五六次就把内存占满,因此基本确定是JDBC部分的问题。

?

第四步,增加了PreparedStatement批量操作的清理功能,比如pstat.clearBatch();,发现用处不大,依然问题依旧。

?

第五步,分析发现19万分每次5000笔也是38次,38次connnection和PreparedStatement可能有问题。因此修改代码,将链接和PreparedStatement都提出来,在循环之外。然后每次批量执行完毕都执行清理操作(pstat.clearBatch())。内存稳定,没有增加,问题解决。

?

结论:PreparedStatement批量执行方式占用内存有可能非常大(跟批量数据量有关系),如果只是使用close,包括connection的close,并不能及时释放,哪怕是强制gc也不能释放。

? ? ? ? ? 解决的方法就是使用统一个PreparedStatement,那么假设它占用50M的空间,循环使用的情况下,只是覆盖没有新new一些地址,可能就是解决问题的思路。

?

不过上述结论没有经过全面测试和知识分析还存在一些疑问。

?

1,db2和oracle是否都这样?

2,怎么样才是PreparedStatement真正释放的方法,跟数据库连接池是否有关系。为什么我们原始代码不能及时gc掉呢?

3,数据迁移最佳模式是什么,我们做的是否是最佳的。我觉得只能是最简单的。

?