java.sql.ResultSet close

Leonid Kudryavtsev
Дата: 10.03.2011 14:02:24
Проблема на custom коде под Oracle Utilities Customer Care & Billing. Продукт использует Cobol, Hibernate, C3P0, Oracle thin JDBC.

Java документация говорит:

http://download.oracle.com/javase/6/docs/api/java/sql/ResultSet.html
A ResultSet object is automatically closed when the Statement object that generated it is closed, re-executed, or used to retrieve the next result from a sequence of multiple results.


Проблема на Linux JDBC
Кусок_из_лога
Oracle driver statement cache enabled with size of 50

Database: Oracle, version: Oracle Database 10g Enterprise Edition Release 10.2.0.5.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

JDBC driver: Oracle JDBC driver, version: 11.1.0.6.0-Production


При коде
pstmt = conn.prepareStatement( "...выбор 1 строчки из 3 полей...." );
while ( qi.hasNext() ) {   // Цикл 100 раз
  java.sql.ResultSet rset;
  pstmt.setString( 1, e.getId().getIdValue() );
  rset = pstmt.executeQuery();
  if ( rset.next() ) {
...
    }
  // Здесь забыли rset.close();
}
pstmt.close();

Ненормально высокое потребление памяти, не очищающаяся GC, почти до 200 метров + куча памяти чистящейся GC. Т.е. по >2 метров на ResultSet из 1 строки и 3 коротеньких полей.

Что это за фигня? На Windows и JDBC driver: Oracle JDBC driver, version: 10.2.0.1.0 такого эффекта на том же коде нет. Максимальное потребление памяти 20-30 метров (всего).

Что за фигня?
1. Куда уходит так много памяти на ОДНОМ ResultSet'е. На мой взгляд, 2 Mb это перебор.
2. Почему нет описанного в Java documentation поведения: ResultSet object is automatically closed when the Statement object that generated it is ... re-executed.
3. Почему при чистке young generation GC (стандартный Copy Garbage Collector) объекты RecordSet не чистятся? Ссылки на них нет, они IMHO должны очищаться и как минимум close'дся в финализаторе. Где может висеть на них ссылка (JDBC, C3P0, Hibernate)?
4. Почему настолько разительное потребление в памяти между Windows и Linux

Проблема в том, что JDBC driver 11.1.0.6.0 посоветовали поставить vendor приложения (Oracle), после того, как была открыта бага с Out of heap space и Memory Leak. Т.ч. менять версию драйвера админы не сильно хотят. Они вообще с.... (редиски) ничего не хотят делать, со словами, что проблема в коде.

Сервер периодически, даже без этой проблемы в коде, работает в режиме 10-5% свободной памяти в Heap (2Gb), что там занимает столько места при достаточно средней нагрузке - не понятно. GC память вычистить не может. Что блокирует объекты от чистки GC - х.з.

Собственно продукт использует Hibernate и C3P0. при этом по настройкам:
1) C3P0 Statements cache вроде отключен:

idleConnectionTestPeriod -> 300, initialPoolSize -> 1, maxIdleTime -> 300, maxPoolSize -> 150, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 1, nestedDataSource
2) В Hibernate настроек на statement cache не нашел
3) В Коболовской VM:
spl.runtime.oracle.statementCacheSize=50
spl.runtime.cobol.sql.disableQueryCache=false
что делают - Х.З. Но явно значение 50 не сильно завышено. На моей Windows машине, где ошибка НЕ воспроизводится - 300
Андрей Панфилов
Дата: 10.03.2011 14:08:41
Leonid Kudryavtsev
3. Почему при чистке young generation GC (стандартный Copy Garbage Collector) объекты RecordSet не чистятся? Ссылки на них нет, они IMHO должны очищаться и как минимум close'дся в финализаторе. Где может висеть на них ссылка (JDBC, C3P0, Hibernate)?
... что там занимает столько места при достаточно средней нагрузке - не понятно. GC память вычистить не может. Что блокирует объекты от чистки GC - х.з.
Снимите heap dump и анализируйте с помощью mat

PS. топик не по форуму.
Leonid Kudryavtsev
Дата: 10.03.2011 14:46:09
Андрей Панфилов
Снимите heap dump

Сняли. Видим кучу не наших объектов (cobol, hibernate) и немного (и в Mb и в количествах) наших.
+ PreparedStatement'ов 600 штук

Андрей Панфилов
с помощью mat

Можешь на mat кинуть ссылку?

Андрей Панфилов
PS. топик не по форуму.


1) поведение Oracle CC&B и Oracle JDBC драйвера разное от OS/версии драйвера.
2) отличие поведения Oracle JDBC драйвера от описанного в Sun Java документации
Leonid Kudryavtsev
Дата: 10.03.2011 14:48:32
MAT это http://www.eclipse.org/mat/ ? Он 2 Gb dump-файл откроет ?

Сейчас пытаемся пользоваться http://www.ej-technologies.com/products/jprofiler/overview.html
Андрей Панфилов
Дата: 10.03.2011 14:56:36
Leonid Kudryavtsev
MAT это http://www.eclipse.org/mat/ ? Он 2 Gb dump-файл откроет ?
Да он, на x86 жаве не откроет (предел в районе 1Gb), на x86_64 с задранным хипом без проблем.
Leonid Kudryavtsev
Дата: 10.03.2011 15:26:37
Что видим сейчас:

Почти вся память занята под:
37 com.mchange.v2.c3p0.impl.NewPooledConnection (1.9 Gb)

в которых, судя по всему цепляют:
768 шт oracle.jdbc.driver.T4CPreparedStatement (1.893 Mb)

и рядышком лежит
763 шт. - org.hibernate.loader.collection.OneToManyLoader (1.893 Mb)

Все бы ничего. Но:
1) Почему T4CPreparedStatement такие большие?
2) И зачем они лежат. Вроде statement cache в C3P0 отключен.
Андрей Панфилов
Дата: 10.03.2011 16:09:34
Leonid Kudryavtsev,

выложите дамп куда-нибудь, если не страшно.
dba123
Дата: 11.03.2011 09:01:16
Leonid Kudryavtsev,

pstmt = conn.prepareStatement( "...выбор 1 строчки из 3 полей...." );
while ( qi.hasNext() ) {   // Цикл 100 раз
  java.sql.ResultSet rset;
  pstmt.setString( 1, e.getId().getIdValue() );
  rset = pstmt.executeQuery();
  if ( rset.next() ) {
...
    }
  // Здесь забыли rset.close();
}
pstmt.close();

а зачем объявлять ResultSet внутри цикла
я скорее не программист, но
- вынес его наверх бы
- использовал бы везде где возможно пакет oracle.jdbc (OracleConnection, OracleResultSet, OraclePreparedStatement,...)
тут есть одна глава из книги, можно посмотреть jdbc_ch5
Leonid Kudryavtsev
Дата: 11.03.2011 16:01:31
Всем огромное спасибо за помощь. По результатам постараюсь отчитаться.

Пока понятны следующие проблемы, их сейчас и решаем:
1. ВСЕГДА нужно явно закрывать PreparedStatement/CallableStatement и крайне желательно ResultSet.
Что IMHO автоматически означает, что стандартными классами пользоваться ВООБЩЕ нельзя. Т.к. если что-то где-то можно забыть, хоть один программист из команды забудет.
Сейчас весь код переписываем на наш личный класс-прокладку и все вызовы PreparedStatement из кода убиваем.
2. connection pooling
В JDBC явные ссылки от Connection к Statement к ResultSet. Поэтому при наличие в коде:
PreparedStatement pstmt;
pstmt = conn.prepareStatement();
...
pstmt = null;
На самом деле, PreparedStatement через GC может не убиваться. Т.к. есть такая замечательная вещь, как connection pooling, который держит Connection и соответственно PreparedStatement'ы. Поэтому их всегда, как минимум, гарантированно нужно закрывать.
3. Statement cache - пока не понятно, как это у нас сказывается на систему. Судя по всему, вещь тоже веселая. По крайне мере в дампе от падений, куча soft reference (> 1.5 Gb), которые вроде должны были, но не почистились. Играем с параметрами.
4. JDBC - тестируем разные версии драйверов. Очень не нравиться не нормально высокое потребление памяти.
5. Параметры системы
6. Параметры памяти - очень хочется нормально настроить young область (задрать eden) + возможно будем играться с -XX:SoftRefLRUPolicyMSPerMB и -Dsun.rmi.dgc.client.gcInterval/-Dsun.rmi.dgc.server.gcInterval

Андрей Панфилов
выложите дамп куда-нибудь, если не страшно.

на сторону не могу. Но у нас компания не маленькая ))), т.ч. дампы и ребятам из соседних отделов выдали - для их экспертной оценки