Java jdbcpreparedStatement 超出了 OutOfMemoryError GC 开销限制
Posted
技术标签:
【中文标题】Java jdbcpreparedStatement 超出了 OutOfMemoryError GC 开销限制【英文标题】:OutOfMemoryError GC overhead limit exceeded for java jdbc preparedStatement 【发布时间】:2014-11-20 07:22:24 【问题描述】:我正在编写一个工具,用于将数据从旧模式传输到 oracle 数据库中的新模式。
我的数据库中有大约 20 个表。其中只有两个很大,它们可能有四百万条记录。其他的都很小(可能是一万或十万)。
现在我用一个线程串行处理所有的小表,把大表分成几块,创建一个线程,用一个线程处理一块,每块一百万条记录。
现在我有一些问题。当我启动程序时,一切正常。但是当我的程序运行一段时间后,我会得到一些错误信息:
Exception in thread "Thread-8" java.lang.OutOfMemoryError: GC overhead limit exceeded
at oracle.jdbc.driver.OracleBlobInputStream.needBytes(OracleBlobInputStream.java:168)
at oracle.jdbc.driver.OracleBufferedStream.readInternal(OracleBufferedStream.java:178)
at oracle.jdbc.driver.OracleBufferedStream.read(OracleBufferedStream.java:147)
at oracle.jdbc.driver.OracleBufferedStream.read(OracleBufferedStream.java:137)
at oracle.jdbc.driver.BlobAccessor.getBytes(BlobAccessor.java:249)
at oracle.jdbc.driver.OracleResultSetImpl.getBytes(OracleResultSetImpl.java:714)
at oracle.jdbc.driver.OracleResultSet.getBytes(OracleResultSet.java:1625)
at datatransfer.processor.CProcessor.write(CProcessor.java:111)
at datatransfer.processor.Processor.process(Processor.java:77)
at datatransfer.thread.CThread.run(CThread.java:37)
我检查了我的程序,没有闭环,我真的关闭了statement
和resultset
。
每个Thread
都有自己的Connection
。
如何检查我的程序消耗内存的原因?有没有办法解决这个问题?
ResultSet rs = statement.executeQuery(sql);
int count = 0;
long start = System.currentTimeMillis();
while(rsSrc.next())
preStatement.setString(1, rsSrc.getString(1));
preStatement.setString(2, rsSrc.getString(2));
preStatement.setString(3, rsSrc.getString(3));
preStatement.setString(4, rsSrc.getString(4));
preStatement.setString(5, rsSrc.getString(5));
preStatement.setString(6, rsSrc.getString(6));
preStatement.addBatch();
count++;
if (count % batchSize == 0)
preStatement.executeBatch();
preStatement.clearBatch();
preStatement.executeBatch();
preStatement.clearBatch();
writeConn.commit();
long end = System.currentTimeMillis();
statement
和preStatement
是由不同的Connection
创建的,一个是旧架构,另一个是新架构。
我的代码有问题吗?
【问题讨论】:
尝试使用 -XX:+HeapDumpOnOutOfMemoryError jvm arg 运行您的应用程序,它将吐出堆转储,然后您可以在分析器上分析您的堆。如果堆太低,请尝试增加堆 -Xmx3G。 【参考方案1】:尝试使用 jvisualvm 分析 RAM 中的实例/对象创建,它通常会立即告诉您是否泄漏。 (这是一个 GUI,不要惊慌 ;-))
文档 -> https://docs.oracle.com/javase/6/docs/technotes/tools/share/jvisualvm.html
它是一个分析器,因此它会向您显示您在哪里花费的时间、您有多少类实例,以及您的应用运行时的基本情况。
在linux上默认安装了官方的oracle jdk!
如果内存使用量几乎恒定但处于边缘,请尝试增加堆(例如 -Xmx2G)
【讨论】:
【参考方案2】:就我而言,我不得不将 Matlab 中的数十万个 INSERT
语句发送到数据库中。我也得到了 GC 开销异常:
java.sql.SQLException: java.lang.OutOfMemoryError: GC overhead limit exceeded
我的解决方案是,每隔几千(在本例中为 2000)INSERT 镜头关闭数据库连接,从 Matlab 工作区中删除并清除对象。当然要随后打开一个新的连接。
classdef mysqlService < handle
properties
db;
counter = 0;
dblimit = 0;
end
methods
function x = executeQuery(obj, query)
obj.counter = obj.counter + 1;
if (obj.counter > obj.dblimit + 2000)
obj.dblimit = obj.counter;
delete(obj.db);
clear obj.db;
import lib.queryMySQL.src.edu.stanford.covert.db.MySQLDatabase;
obj.db = MySQLDatabase('localhost:3306', 'fani_dev', 'root', 'dev1');
end
obj.db.prepareStatement(query);
x = obj.db.query();
end
function obj = MySqlService()
import lib.queryMySQL.src.edu.stanford.covert.db.MySQLDatabase;
obj.db = MySQLDatabase('localhost:3306', 'fani_dev', 'root', 'dev1');
end
end
end
脚本现在可以正常工作了。 CPU 工作负载和 RAM 使用率似乎也不错。
【讨论】:
这个答案是 Matlab 语言,但同样适用于 Java。通过遵循这个建议,我的程序在启动后 1 分钟从 1GB 内存下降到 56mb。循环和准备语句使它像滚雪球一样增长。打开和关闭连接可能会花费您更多的时间来处理,但它可以避免 ram 和 GC。【参考方案3】:该问题的一个可能解决方案是增加 Eclipse 可用的堆大小。您可以在打开 eclipse.ini 文件时执行此操作,该文件位于 eclipse 安装文件夹中。
打开文件后,您可以添加 -Xmx2048M,这将为您的 Eclipse 提供 2 GB 的可用堆。
此解决方案取决于您的系统有多强大以及您可以为 Eclipse 提供多少整个堆。
For more information click here...
解决问题的另一种方法是尝试为大表处理较小的部分(块)。
如果您想深入了解并找到 OOM 的具体原因,您可以创建一个堆转储(或几个堆转储)并使用 SAP 和 IBM 免费提供的http://www.eclipse.org/mat/ 对其进行分析。这是一个非常强大的工具。
【讨论】:
非常感谢,我已经使用分析工具检查堆,我发现什么时候内存成本会随着程序的运行而增加。PreparedStatement.executeBatch()
会导致这个问题吗?我会尽量用小块来处理大表。
程序运行时内存使用量增加是正常现象。更有趣的是确切地查看哪些对象分配了大部分内存。这些对象将是您正在寻找的内存泄漏。以上是关于Java jdbcpreparedStatement 超出了 OutOfMemoryError GC 开销限制的主要内容,如果未能解决你的问题,请参考以下文章