最近在开发一款向数据库中添加Mock数据的命令行工具时,出现了 java.lang.OutOfMemoryError这种问题,工具大概在插入150万条数据总会出现这个问题。本篇博客就记录了如何解决这一问题的过程:
为了进一步学习MySQL数据库的相关知识,需要操作存放大量数据的数据表;为此,出现了开发一款向数据库中添加Mock数据的命令行工具这一日常需求。实现的目标:只需在相关文件中进行配置,便可进行插入数据。
开发的关键步骤是获取数据表的字段名称、字段类型、相关的约束(比如varchar的长度等);下面是获取相关信息的关键代码:
1 | PreparedStatement pst = conn.prepareStatement(sql); |
获取到数据表的字段信息后,接下来就是根据字段的类型、约束来生成相应的数据;然后就是不断地往数据表中插入数据。
但是,程序总会在插入到150万条左右数据的时候出现 java.lang.OutOfMemoryError的问题,如下图:

出现问题的初始代码如下:(测试时insertNum为1000万,perCount为100)
1 | PreparedStatement pst; |
刚出现这问题的时候,直接想到可能是因为代码中的字符串太多,导致常量池中的字符串对象数据过多而导致的OOM问题,于是乎将代码中的String都使用StringBuilder来替换。这一方法还是无法避免OOM的出现,似乎对内存的利用没有丝毫帮助。
接下来该如何来解决问题呢?我想肯定不能完全依靠自己的直觉,而是要按照规范的流程来度量问题产生的原因。下面是进行的一些步骤:
jps:获取应用的进程号;jinfo -flags pid:获取应用运行时设置的JVM参数,比如-XX:MaxHeapSize等;jvisualvm:启动Java VisualVM工具;
获取到MaxHeapSize后,如果认为其较小,可以将其调大。但是我这个问题无法通过调整MaxHeapSize来解决,因为通过VisualVM工具分析,应用消耗的内存一直持续增大,如果不修改代码,根本无法完成最后的目标。下面是内存的消耗情况:

通过Java VisualVM上的Profiler标签的内存按钮来展示性能分析结果,发现char[]占用的内存极其多,而String类底层实现的原理就是char[]。
接下来通过监视标签的堆Dump按钮来产生.hprof文件,可以使用MAT软件来打开该文件,进一步进行分析。对于我这个问题产生的.hprof文件,MAT分析的结果如下:

从上面的图片可以很清楚地看出,JDBC4Connection这个实例占用了极大的内存,从而很快定位到问题的地方:pst = conn.prepareStatement(sql),该方法在循环体中调用,插入140万数据时,该方法调用了1.4万次。结合MAT分析的结果,每次调用pst = conn.prepareStatement(sql)时,conn都会将sql字符串进行保存,即保存了1万多个sql字符串,可想而知占用了多大的内存。
当然这个问题可以通过两种方法解决(具体代码见):
- 不使用
PreparedStatement预编译的方式; - 正确地使用
PreparedStatement预编译的方式