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)

我检查了我的程序,没有闭环,我真的关闭了statementresultset

每个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();

statementpreStatement 是由不同的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 开销限制的主要内容,如果未能解决你的问题,请参考以下文章

Java 布尔运算

java [Java] Java常用代码#java

Java - 35 Java 实例

Java While 循环

Java 字符串

Java If ... Else