最近在开发一款向数据库中添加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
预编译的方式