В чем причина OutOfMemoryError: пространство кучи Java в следующем случае? - PullRequest
4 голосов
/ 19 ноября 2011

Следующий пример кода находится внутри цикла for, который выполняется около 2 миллионов раз.

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 ]

13 голосов
/ 19 ноября 2011

Судя по комментариям, вы, кажется, создаете Statement и ResultSet внутри цикла, но никогда не закрываете их. Вы должны также закрыть их в цикле. Это освободит внутренние ресурсы.

Кроме того, вы не пользуетесь кешем БД подготовленного оператора. Прямо сейчас вы объединяете строку параметра в строке 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. Э.Г.

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

Это, однако, сложнее с заполнителями. См. Также: Каков наилучший подход с использованием JDBC для параметризации предложения IN?

Или в качестве совершенно другой альтернативы, если необходимо, с помощью более опытного администратора БД / ниндзя SQL, переписать все это так, чтобы вы получили точно результаты, которые вам нужны, только с один SQL-запрос. Задайте, если необходимо, отдельный вопрос об этом на здесь на SO.

5 голосов
/ 19 ноября 2011

После того, как вы закончите с циклом while, rs2.close() & stmt2.close() поможет.

1 голос
/ 16 мая 2013

Существует другой метод разрешения, если вы не хотите увеличивать размер кучи 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 ();

теперь вы будете получать строки результатов одну за другой

1 голос
/ 19 ноября 2011

Я хотел бы уточнить некоторые аспекты использования ресурсов JDBC.

  1. Лучшая практика для всех ресурсов - закрывать их в блоке finally. Вы уже приводили примеры с ResultSet и PreparedStatement.

  2. Если ваше приложение не использует пул соединений и вы вызываете connectcion.close (), на самом деле оно должно закрывать объекты ResultSet и PreparedStatement. Реализации соединений, как правило, хранят все эти объекты внутри, и даже если вы создадите их в цикле, они будут закрыты.

  3. Если используется пул соединений, то, когда вы вызываете connection.close (), вы не закрываете само физическое соединение, а просто возвращаете его обратно в пул. В результате объекты ResultSet и PreparedStatement не закрываются. Я настоятельно рекомендую вам прочитать следующую статью об этом Подключить утечки памяти в корпоративных приложениях Java

  4. В Java7 и Statement, и ResultSet расширяют AutoCloseable, так что вы также можете не беспокоиться о их закрытии. (Опять же, если вы используете пулы соединений, я не думаю, что Java 7 сможет закрыть их автоматически)

...