# Memory leaks bei DB Abfrage



## Rol (27. Jul 2011)

Hi,

ich rufe aus einer MySQL DB mehre Millionen Datensätze ab. Dabei wird dan Programm allmählich immer langsamer. Ein Test mit dem Profiler von NetBeans zeigt eine ständig wachsende Zahl an Surviving Genaration. Bei jedem GC Lauf wird es scheinbar eine mehr.

Soweit ich weiß deute das auf memory leaks hin (richtig?).

Ich habe schon die SQL Abfrage in einzelne Teile unterteilt (ich brauch die Daten sowieso nicht alle geleichzeitig sondern sequentiell). Das bringt keine Verbesserung. Hier der Code:

```
public void test() {
        startDate = (Date) backtestGUI.startDateChooser.getDate();
        endDate = (Date) backtestGUI.endDateChooser.getDate();
        Connection con = Main.sampleFrame.historicalDatabase.con;
        Statement stmt = null;
        try {
            stmt = con.createStatement();
        } catch (SQLException ex) {
            System.out.println("SQL Exception: " + ex.toString());
        }
        ResultSet rs = null;
        int startTs = (int) (startDate.getTime() / 1000);
        int endTs = (int) (endDate.getTime() / 1000);
        int maxPerExecution = 500;
        int startEx = startTs;
        int endEx = startEx;
        while (endEx < endTs) {
            endEx = startEx + maxPerExecution;
            if (endEx > endTs) {
                endEx = endTs;
            }
            String SQL = "SELECT DISTINCT time, open, close, high, low FROM historicaldata WHERE symbol='foo' AND time >= " + startEx + " AND time < " + endEx + " ORDER BY time";
            try {
                stmt = con.createStatement();
            } catch (SQLException ex) {
                System.out.println("SQL Exception: " + ex.toString());
            }
            try {
                rs = stmt.executeQuery(SQL);
            } catch (SQLException ex) {
                System.out.println("SQL Exception: " + ex.toString());
            }
            //hier wird eigentlich eine Methode aufgerufen und das resultSet übergeben...
            //doSomething(rs);
            //...aber auch ohne diese "Nutzleistung" steigen "Used Heap" und "Surviving Generations" stetig an.
            startEx = endEx;
        }
        try {
            stmt.close();
        } catch (SQLException ex) {
            System.out.println("Error while close stmt");
            Logger.getLogger(BacktestEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
        try {
            rs.close();
        } catch (SQLException ex) {
            System.out.println("Error while close rs");
            Logger.getLogger(BacktestEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
```

Den Aufruf der eigentlichen "Nutzmethode" habe ich mal rauskommentiert damit die als Ursache rausfällt.
Wie gesagt, "Used Heap" und "Surviving Generations" steigen und das Programm wird immer langsamer. Ich habe die Heapsize schon auf 1500mb vergrößert. Das bringt zwar Linderung, aber hier muß man doch prinzipiell was besser machen können.

MfG
Rol


----------



## SlaterB (29. Jul 2011)

die Zeilen 6-10 können gestrichen werden wenn du in der Schleife eh noch neues Statement erzeugst?
und nur das letzte Statement wird derzeit geschlossen, ResultSet ebenso, vielleicht hilft dort mehr Arbeit in der Schleife

und immer ResultSet vor dem Statement schließen


----------



## DerEisteeTrinker (29. Jul 2011)

Warum benutzt du kein PreparedStatement? Da kannst das ResultSet nach der Arbeit schließen und dann vom PreparedStatement die Parameter leeren lassen und es wiederverwenden. Spart dir schon mal die Anlage des Statements.

heißt:

Connection aufbauen
PreparedStatement anlegen
Parameter setzen
ResultSet verarbeiten
ResultSet schließen
Parameter leeren
neue Parameter setzen
... ResultSet verarbeiten ...
PreparedStatement schließen


----------



## Rol (29. Jul 2011)

Danke für die Vorschläge. Ich habe es jetzt mit PreparedStatement gemacht:


```
//Connection aufbauen
	//hab' ich schon.
        //PreparedStatement anlegen
        java.sql.PreparedStatement ps = null;
        try {
            ps = con.prepareStatement("SELECT ... WHERE time >= ? AND time < ? ORDER BY time");
        } catch (SQLException ex) {
            Logger.getLogger(BacktestEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
        while (endEx < endTs) {
            endEx = startEx + maxPerExecution;
            if (endEx > endTs) {
                endEx = endTs;
            }
	    //Parameter setzen
            try {
                ps.setLong(1, startEx);
            } catch (SQLException ex) {
                Logger.getLogger(BacktestEngine.class.getName()).log(Level.SEVERE, null, ex);
            }
            try {
                ps.setLong(2, endEx);
            } catch (SQLException ex) {
                Logger.getLogger(BacktestEngine.class.getName()).log(Level.SEVERE, null, ex);
            }
            try {
                rs = ps.executeQuery();
            } catch (SQLException ex) {
                Logger.getLogger(BacktestEngine.class.getName()).log(Level.SEVERE, null, ex);
            }
            //ResultSet verarbeiten
            //hier kommt dann wieder meine "Nutzmethode".
            startEx = endEx;
	    //ResultSet schließen
            try {
                rs.close();
            } catch (SQLException ex) {
                Logger.getLogger(BacktestEngine.class.getName()).log(Level.SEVERE, null, ex);
            }
            //Parameter leeren
            try {
                ps.clearParameters();
            } catch (SQLException ex) {
                Logger.getLogger(BacktestEngine.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
	//PreparedStatement schließen
	try {
            ps.close();
        } catch (SQLException ex) {
            Logger.getLogger(BacktestEngine.class.getName()).log(Level.SEVERE, null, ex);
        }
```

Die Surviving Generation im Profiler wachsen weiter stetig über die ganze Laufzeit an.


----------



## SlaterB (29. Jul 2011)

ich kann leider nichts weiter beitragen, möchte aber noch im Namen der Menschheit darauf hinweisen, dass du jede
[c]Logger.getLogger(BacktestEngine.class.getName()).log(Level.SEVERE, null, ex);[/c]
Zeile durch [c]severe(ex);[/c] ersetzen könntest, bei Vorhandensein entsprechender Hilfsmethode,

üblich ist ansonsten auch, wenigstens 
[c]static Logger log = Logger.getLogger(BacktestEngine.class.getName())[/c]
zu definieren und dann log zu verwenden, statt immer die Langversion


----------



## maki (29. Jul 2011)

> Die Surviving Generation im Profiler wachsen weiter stetig über die ganze Laufzeit an.


Von welcher Klasse eigentlich?


----------



## turtle (30. Jul 2011)

Daher bin ich der Meinung, dass man NICHT mit selbst geschriebenem JDBC-Code hantieren sollte. 

Die korrekte Freigabe der Resourcen (Statements, Resultset, Connection,...) sollte man einem Framework überlassen (myBatis, JPA, Hibernate, whatever).


Gibts am Ende eine OOM-Exception?


----------



## maki (30. Jul 2011)

Ich sehe das ähnlich Turtle, man sollte JDBC Code mal programmiert haben dass man weiss was so unter der Haube ist wenn man iBatis/myBatis oder gar ORM nutzt, aber in prod. Projekten muss man schon genau abwägen ob die Komplexität und Fehlerträchtigkeit die mit so einer low-level API einhergehen wirklich wünschenswert sind, abgesehen davon das richtiger JDBC immer sehr hässlich ist (geschachtelte try/catch Blöcke).

Aber im Moment wissen wir nichtmal das angebliche Memoryleak überhaupt durch JDBC verursacht wird


----------



## Rol (2. Aug 2011)

maki hat gesagt.:


> Von welcher Klasse eigentlich?



Die meisten surviving generation sgibt es von den Klassen:
char[] mit ca. 24% der Live Bytes
java.lang.String  mit ca. 10% der Live Bytes
java.awt.Dimensionmit ca. 0,3% der Live Bytes

Alle drei habem die max. surviving generation.


----------



## Rol (2. Aug 2011)

turtle hat gesagt.:


> Daher bin ich der Meinung, dass man NICHT mit selbst geschriebenem JDBC-Code hantieren sollte.
> 
> Die korrekte Freigabe der Resourcen (Statements, Resultset, Connection,...) sollte man einem Framework überlassen (myBatis, JPA, Hibernate, whatever).


o.k., das ist 'ne Ansagen. Ich habe davon leider (noch) gar keine Auhnung. Kannst Du mir aufgrund der grob umrissenen Aufgabenstellung ein Framework empfehlen? Viell. eines mit einem guten Tutorial?



turtle hat gesagt.:


> Gibts am Ende eine OOM-Exception?


Nein.


----------



## maki (2. Aug 2011)

Dann liegt das Problem imho nicht am JDBC Treiber bzw. dessen Nutzung, oder zumindest gibt es keinen Hinweis darauf.
Du solltest dir mit VisualVM anzeigen lassen, von welchen Objekten die 3 erzeugt werden.

Nebenbei, du erstellst Strings hoffentlich nicht mit new


----------



## Rol (2. Aug 2011)

maki hat gesagt.:


> Du solltest dir mit VisualVM anzeigen lassen, von welchen Objekten die 3 erzeugt werden.


Ich arbeite mit Netbeans, VisualVM ist doch ein plugin für eclipse, oder ?



maki hat gesagt.:


> Nebenbei, du erstellst Strings hoffentlich nicht mit new


Nein


----------



## turtle (2. Aug 2011)

Ich empfehle IMMER mybatis. Das englisch-sprachige Tutorial ist sehr gut. Für  den Vorgänger iBATIS-2 gibt es auch ein deutsch-sprachiges Tutorial.

Und grundsätzlich gilt: mybatis ist wirklich einfach einzusetzen. Von 0 bis zum ersten Zugriff auf eine DB in Java in ca 5 Minuten!


----------



## Rol (2. Aug 2011)

turtle hat gesagt.:


> Ich empfehle IMMER mybatis. Das englisch-sprachige Tutorial ist sehr gut. Für  den Vorgänger iBATIS-2 gibt es auch ein deutsch-sprachiges Tutorial.
> 
> Und grundsätzlich gilt: mybatis ist wirklich einfach einzusetzen. Von 0 bis zum ersten Zugriff auf eine DB in Java in ca 5 Minuten!



Danke! Ich werde mich mal einarbeiten.


----------



## maki (2. Aug 2011)

Rol hat gesagt.:


> Ich arbeite mit Netbeans, VisualVM ist doch ein plugin für eclipse, oder ?


Nö, VisualVm ist ein Plugin für NetBeans, dass es auch als Standalone SW gibt die mit dem JDK ausgeliefert wird.

Wie gesagt, sehe bis jetzt keinen Grund warum JDBC am Memoryleak schuld sein sollte.


----------



## Empire Phoenix (2. Aug 2011)

Da ichs elber mysl benutze gehe icha uch eher davona us das du einn leak in dienem programmcode hast, der etl irgentwie damit zusammenhängt.


----------



## flenst111 (6. Jan 2015)

API auf API setzen, immer komplexere APIS verwenden. KISS! (Keep it simple ...) Das kann nicht die Lösung sein. Und hier ärgert mich das JDBC, weil es einfach keine transparenten Methoden zum Iterieren durch riesige REsultsets anbietet. M.E. liegt der Fehler in der Konfiguration des JDBC-Treibers. Leider funktionieren die alle unterschiedlich und verhalten sich häufig nicht transdparent. Ich habe alles mögliche probiert und hänge jetzt an diesem Punkt fest. Ich werde dazu in einen gesondertes Thema aufmachen, da ich denke, daß ich viele zusätzlich Fragen habe. Auf alle Fälle scheint man beim createstatement folgende Parameter richtig beantworten zu müssen: _stmt = con.createStatement(resultSetType,resultSetConcurrency,resultSetHoldability);_


----------

