Ich habe eine Anwendung mit der ich u.a. durch riesige Tabellen mit Millionenden von Zeilen iterieren muß. Mich interessiert immer nur die aktuelle Zeile aus dem RESULTSET. Ich habe jetzt schon in etlichen Büchern gewälzt, etliche Vorschläge gelesen und leider keine funktionierende Lösung für die von mir verwendeten JDBC-Treiber für die MAXDB und postgres (JDBC3-Treiber,JDBC4-Treiber für Postgres 9.1,..) gefunden. Manchmal werde ich auch nicht schlau aus den Vorschlägen und man erkennt nicht, ob der Ansatz nun funktioniert hat oder nicht. Ich habe nun schon etliche Tests hinter mir und keine vernünftige Lösung. Ich habe mit unserem SQL-Framework unter Verwendung von c3p0 und ComboPooledDataSource getestet, ich habe nativ mit groovy-sql getestet und diverse Parameter geändert, um den Übeltäter zu finden. Es ist mir nicht gelungen. Ich habe die gleiche Applikation gestartet und mir mit einem Modul, die Daten aus einem JSON-File eingelesen (anstatt das SELECT-Modul zu verwenden) und dabei natürlich keinen Memoryleak erhalten. Der Prtogrammablauf mit den Konfigurationsparametern ist in einem XML-Script erkennbar. (wenn nötig kann ich dazu einen Graph (mit Graphviz erstellt) liefern. Hier das Script:
<?xml version="1.0" encoding="UTF-8"?>
<runner class="servicerunner" verbose="false">
<do doname="startanweisung" classname="BSStarter" verbose="false">
<starte doname="SELECT_TIERE" />
</do>
<do doname="SELECT_TIERE" classname="SelectNext" verbose="false" dbsynonym="SPEKDB.VVVODBA" type="extended" fetchsize="10" resulttyp="FORWARD_ONLY" autocommit="true" concurrency="CONCUR_READ_ONLY" holdability="CLOSE_CURSORS_AT_COMMIT" checkmem="true" erg="auswahl" >
<abfrage doname="selabfrage"
abfrage="select * from calving"/>
<starte doname="auswahl2json" verbose="true" />
<starte doname="count" id="count4a"/>
<starte doname="gacol" id="cmd4b"/>
<starte doname="checkmem" id="cmd5"/>
</do>
<do doname="auswahl2json" classname="Var2JsonFile" verbose="true" variable="auswahl" checkmem="true" jsonfile="/tmp/auswahl.json" />
<do doname="gacol" classname="GaCol" verbose="true" />
<do doname="count" classname="Counter" verbose="false" />
<do doname="checkmem" classname="CheckMemory" memdiff="0" gjbname="memgjb" httpresponse="anfrage.httpresponse" />
</runner>
Dabei wird ein anschließend ein Garbage Collection-Modul aufgerufen und danach der Speicherzustand ausgegeben. In der LOG-Datei steht nach dem 1 Durchlauf
20150106 121425 INFO de.lkv.runner.does.BasicDo:call: checkmem kb::run_end:-auswahl2json:-1-DL:1000: umemKB:164530 freeKB:147277 maxKB:1720320
Nach dem 7600 Durchlauf
20150106 130055 INFO de.lkv.runner.does.BasicDo:call: checkmem kb::run_start:-auswahl2json:-1-DL:7600: umemKB:173548 freeKB:125459 maxKB:1720320
umemKB gibt den von der Applikation benutzten Speicher aus. Man sieht auch hier deutlich den Zuwachs trotz Garbage-Collection, den man natürlich in der Produktion nicht so wie in diesem Fall einsetzen würde. Die Selectklasse sieht wie folgt aus:
Die Methode, mit der die jeweils aktuelle Zeile abgearbeitet wird, sieht wie folgt aus:
<?xml version="1.0" encoding="UTF-8"?>
<runner class="servicerunner" verbose="false">
<do doname="startanweisung" classname="BSStarter" verbose="false">
<starte doname="SELECT_TIERE" />
</do>
<do doname="SELECT_TIERE" classname="SelectNext" verbose="false" dbsynonym="SPEKDB.VVVODBA" type="extended" fetchsize="10" resulttyp="FORWARD_ONLY" autocommit="true" concurrency="CONCUR_READ_ONLY" holdability="CLOSE_CURSORS_AT_COMMIT" checkmem="true" erg="auswahl" >
<abfrage doname="selabfrage"
abfrage="select * from calving"/>
<starte doname="auswahl2json" verbose="true" />
<starte doname="count" id="count4a"/>
<starte doname="gacol" id="cmd4b"/>
<starte doname="checkmem" id="cmd5"/>
</do>
<do doname="auswahl2json" classname="Var2JsonFile" verbose="true" variable="auswahl" checkmem="true" jsonfile="/tmp/auswahl.json" />
<do doname="gacol" classname="GaCol" verbose="true" />
<do doname="count" classname="Counter" verbose="false" />
<do doname="checkmem" classname="CheckMemory" memdiff="0" gjbname="memgjb" httpresponse="anfrage.httpresponse" />
</runner>
Dabei wird ein anschließend ein Garbage Collection-Modul aufgerufen und danach der Speicherzustand ausgegeben. In der LOG-Datei steht nach dem 1 Durchlauf
20150106 121425 INFO de.lkv.runner.does.BasicDo:call: checkmem kb::run_end:-auswahl2json:-1-DL:1000: umemKB:164530 freeKB:147277 maxKB:1720320
Nach dem 7600 Durchlauf
20150106 130055 INFO de.lkv.runner.does.BasicDo:call: checkmem kb::run_start:-auswahl2json:-1-DL:7600: umemKB:173548 freeKB:125459 maxKB:1720320
umemKB gibt den von der Applikation benutzten Speicher aus. Man sieht auch hier deutlich den Zuwachs trotz Garbage-Collection, den man natürlich in der Produktion nicht so wie in diesem Fall einsetzen würde. Die Selectklasse sieht wie folgt aus:
Java:
public Select(String dbsynonym, String sqlstr,int fetchsize,int resultSetType, int resultSetConcurrency,int resultSetHoldability,boolean autocommit) throws SQLException {
this.sqlstr = sqlstr;
this.dbsynonym = dbsynonym;
con = null;
try {
dsm = DataSourceManager.getInstance();
con = dsm.getConnection(dbsynonym);
con.setAutoCommit(autocommit);
log.debug("testselect:" + sqlstr);
// stmt = con.createStatement();
stmt = con.createStatement(resultSetType,resultSetConcurrency,resultSetHoldability);
// stmt = con.createStatement(ResultSet.FETCH_FORWARD,java.sql.ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(fetchsize);
rs = stmt.executeQuery(sqlstr);
meta = rs.getMetaData();
columnlist = SQLUtils.getColumnNameList(meta);
metacolumns = SQLUtils.retrieveMetaData(meta);
anzcols = meta.getColumnCount();
anzrows = 0;
meta = null;
} catch (LKVException ey) {
log.error("LKVException:", ey);
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (con != null) con.close();
} catch (Exception ex) {
log.error("Close zweifelhaft " + dbsynonym + " " + sqlstr, ex);
}
} catch (SQLException e) {
mStatus = false;
mSqlcode = e.getErrorCode();
mSQLMessage = e.getMessage();
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (con != null) con.close();
}
catch (Exception ex) {
log.error("Close zweifelhaft " + dbsynonym + " " + sqlstr, ex);
}
if (SQLUtils.checkDontThrow(dbsynonym, e)) {
log.info("SQLException mit Code " + mSqlcode
+ " wurde abgefangen. Message:\n " + e.getMessage());
} else
throw e;
}
}
Die Methode, mit der die jeweils aktuelle Zeile abgearbeitet wird, sieht wie folgt aus:
Java:
public Map getnext() throws SQLException {
try{
if (rs.next()) {
return SQLUtils.transferData(rs, columnlist, metacolumns);
} else {
return null;
}
} catch (SQLException e) {
mStatus = false;
mSqlcode = e.getErrorCode();
mSQLMessage = e.getMessage();
log.error(mSQLMessage,e);
} catch (Exception e) {
log.error("LKVException:", e);
}
return null;
}