OutOfMemoryError的原因是什么:以下情况下的Java堆空间?

时间:2011-11-18 21:24:53

标签: java mysql jdbc memory-leaks heap

以下代码示例位于一个运行大约200万次的for循环中。

List<String> parameters = new LinkedList<String>();
stmt2 = null;
rs2= null;

//This is line 472
stmt2 = con.prepareStatement("select NAME from TABLE_NAME where FIELD="+ strId);
rs2 = stmt2.executeQuery();

while (rs2.next()) {
    parameters.add(rs2.getString("NAME"));
}

堆栈跟踪:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.mysql.jdbc.PreparedStatement.<init>(PreparedStatement.java:437)
    at com.mysql.jdbc.Connection.clientPrepareStatement(Connection.java:2185)
    at com.mysql.jdbc.Connection.prepareStatement(Connection.java:4782)
    at com.mysql.jdbc.Connection.prepareStatement(Connection.java:4687)
    at consistencyCheck.ConsistencyCheck.parameterCheck(ConsistencyCheck.java:472)
    at consistencyCheck.ConsistencyCheck.performConsistencyCheck(ConsistencyCheck.java:316)
    at consistencyCheck.ConsistencyCheck.main(ConsistencyCheck.java:198)

如果需要更多信息,请与我们联系。

谢谢。

感谢大家的答案。我会接受BalusC的回答,因为他先回答。不幸的是,由于声誉不足,我无法提出任何其他答案:(

对所有建议增加内存堆的人都要注意。增加内存堆是你永远不应该做的事情,除非你100%确定这是你问题的唯一解决方案。例如,在我的问题中,增加堆可能会“解决”问题,但潜在的错误仍然存​​在。

4 个答案:

答案 0 :(得分:13)

根据评论,您似乎在循环中创建StatementResultSet但从不关闭它们。你也需要在循环中关闭它们。这将释放内部资源。

此外,您并没有真正从预备语句的数据库缓存中受益。现在,您在SQL字符串中串联连接参数,这会导致创建2M String对象而不是1 String对象。在循环之前更好地准备语句。

try {
    // ...
    statement = connection.prepareStatement("select NAME from TABLE_NAME where FIELD=?");

    for ( /* 2M times? */ ) {
        statement.setInt(1, id);

        try {
            resultSet = statement.executeQuery();
            // ...
        } finally {
            if (resultSet != null) try { resultSet.close(); } catch (SQLException ignore) {}
        }
    }
} finally {
    if (statement != null) try { statement.close(); } catch (SQLException ignore) {}
}

或者,您也可以考虑使用IN子句。 E.g。

WHERE field IN (1,2,3,4,5);

但占位符比较复杂。另见:What is the best approach using JDBC for parameterizing an IN clause?

或者作为一个完全不同的替代方案,如果有必要,在更有经验的数据库管理员/ SQL忍者的帮助下,重写整个内容,以便只需一个完全您需要的结果 SQL查询。如果有必要,可以在上就此问一个单独的问题。

答案 1 :(得分:5)

完成while循环后rs2.close() &amp; stmt2.close() 会有所帮助。

答案 2 :(得分:1)

我想澄清一些有关JDBC资源的事情。

  1. 所有资源的最佳实践,以便在最后块中关闭它们。您已经提供了ResultSetPreparedStatement的示例。

  2. 如果您的应用程序不使用连接池并且您调用connecion.close(),实际上它应该关闭ResultSetPreparedStatement个对象。连接实现作为一项规则将所有这些对象保留在内部,即使您在循环中创建它们,它们也将被关闭。

  3. 如果使用连接池,那么,当您调用connection.close()时,您不会自行关闭物理连接,只需将其释放回池中即可。因此,ResultSetPreparedStatement个对象未关闭。我强烈建议您阅读以下有关它的文章Plug memory leaks in enterprise Java applications

  4. 在Java7中,StatementResultSet都扩展了AutoCloseable,因此您也可以不担心关闭它们。 (同样,如果您使用连接池,我认为Java 7将无法自动关闭它们)

答案 3 :(得分:1)

如果您不想增加JVM堆大小,还有另一种解决方法。

首先,你的MySQL版本应该比5.0更新。

其次,Statement.getResultSetType()应为TYPE_FORWARD_ONLY,ResultSetConcurrency应为CONCUR_READ_ONLY(默认值)。

第三,包括以下一行:     1).statement.setFetchSize(Integer.MIN_VALUE的);     2)((com.mysql.jdbc.Statement)STAT).enableStreamingResults();

现在您将逐个获取结果行