[Erledigt] JTable und OutOfMemoryError (Heap)

Mahlzeit,
ich wieder. :slight_smile: Wie immer mit einem Spezialproblem. :eek:
Also… die Anwendung wird schon mit -Xmx128m gestartet. Dennoch kommt es reproduzierbar auf meinem Rechner nach dreimaligem Neuladen (Button-Klick, manuell) der Tabelle zu einem Fehler:

Exception in thread „AWT-EventQueue-0“
java.lang.OutOfMemoryError: Java heap space
at java.util.Locale.toUpperCase(Locale.java:1104)
at java.util.Locale.(Locale.java:246)
Exception in thread „AWT-EventQueue-0“ java.lang.OutOfMemoryError: Java heap space

Ich habe an einigen Ecken schon versucht, die VM zu überreden, Speicher aufzuräumen (System.gc()), ohne Erfolg.
Insgesamt werden aus der MySQL-Datenbank ca. 60.000 Datensätze (Zeilen) a 17 Felder geladen. Das ist wohl schon bei einmaligem Laden nicht wenig, aber wenn der Speicher nicht geräumt wird, erst recht nicht wenig nach mehrmaligem Durchlauf.

Nun die logischen, zugehörigen Fragen dazu:

  1. Kann ich die VM nicht doch überlisten, in der Zeit zwischen dem Betätigen des Buttons den Speicher zu leeren? Wenn ja, wie? Ggf. kann ich ja mit den Mausklicks warten, bis ich denke, dass die 128MB weit genug geleert sind.
  2. M.E. ist die Erhöhung des Speichers auf -Xmx256m auch nur eine Krücke. Dann dauert es nicht 3 sondern 8 Klicks oder so, bis der OoM-Error auftritt, richtig?
  3. Oder ist die einzige Lösung des Problems die Verwendung von Paging?

Hm, klingt mir eher nach einem Problem das Du Objekte initialisierst und nicht für den GC freigibst zum wegräumen. Zudem entscheidet die VM wann der GC losrennt und die nullen wegräumt. Du kommst daher besser wenn Du mal einen Profiler dransetzt und Dir das verhalten des Speichers anschaust wenn Du die DB abfragst.

Zudem ist der Aufruf von System.gc() nur bedingt erfolgreich. Habe mal gelesen das man es mindestens 4mal aufrufen muss um Erfolg zu haben aber das ist in meinen Augen alles Schweinekram, die VM weiß wann sie die Müllabfuhr losschicken muss zum aufräumen. Versuch lieber mal Objekte die nicht benutzt werden mit null zu setzen (obj = null; ). Die räumt der GC dann sicher weg.

Gut Schuß
VuuRWerK :wink:

[QUOTE=VuuRWerK]Hm, klingt mir eher nach einem Problem das Du Objekte initialisierst und nicht für den GC freigibst zum wegräumen. Zudem entscheidet die VM wann der GC losrennt und die nullen wegräumt. Du kommst daher besser wenn Du mal einen Profiler dransetzt und Dir das verhalten des Speichers anschaust wenn Du die DB abfragst.
[/QUOTE]
Nach Programmstart zeigt mit JConsole einen Verbrauch von ca. 82-84MB an. Klicke ich einmal, wird die MySQL-Abfrage ausgeführt, der Speicherbedarf steigt stetig auf ca. 104MB. Dort verbleibt der Wert auch, bis ich erneut klicke. Dann auf 124MB. Wenn ich nun nichts weiter mache, zeigt die Speichernutzungskurve einen Sägezahn. Das heißt, in ca. 1-Minuten-Perioden sinkt der Speicher auf ca. 115MB ab binnen weniger Sekunden, um dann wieder in ca. 50 Sekunden stetig auf 124MB anzusteigen:
Bild 1
Klicke ich dann noch mal, steigt der Speicherbedarf und sprengt das Limit.
Bild 2

[QUOTE=VuuRWerK;6256]
Zudem ist der Aufruf von System.gc() nur bedingt erfolgreich. Habe mal gelesen das man es mindestens 4mal aufrufen muss um Erfolg zu haben aber das ist in meinen Augen alles Schweinekram, die VM weiß wann sie die Müllabfuhr losschicken muss zum aufräumen. Versuch lieber mal Objekte die nicht benutzt werden mit null zu setzen (obj = null; ). Die räumt der GC dann sicher weg.

Gut Schuß
VuuRWerK ;)[/QUOTE]
OK, damit klar wird, worüber wir hier reden - das ist der Code:

    private void getDataFromDB() {
        Statement s = null;
        ResultSet rs = null;
        ArrayList newRow = null;
        // Zeilen freigeben und neues Array erzeugen
        rows = null;
        rows = new ArrayList();
        try {
            s = DBConnector.getConn().createStatement();
            rs = s.executeQuery(getSQLSelectString());
            
            while (rs.next()) {
                newRow = new ArrayList();
                for (int i = 1; i <= FIELDS_IN_GUI; i++) {
                    newRow.add(rs.getObject(i));
                }
                rows.add(newRow);
            }
        } catch (Exception e) {
            JOptionPane.showMessageDialog(null,
                    "Fehler beim laden der Daten
"+e.toString(),
                    "Fehler", JOptionPane.ERROR_MESSAGE);
        }
        s = null;
        rs = null;
        newRow = null;
        System.gc();
        System.gc();
        System.gc();
        System.gc();
    }

Die Methode befindet sich im TableModel und rows ist eine Instanzvariable (ArrayList), die alle Tabellenzeilen enthält.
Beim obigen Code fliegt immer noch der OutOfMemoryError. Dass eine andere Stelle im Code beim Button-Klick fürs Vollsaugen des Speichers sorgt, kann ich nicht erkennen. Da gibt es im Aufruf keine Schleifen mit neu erstellten Objekten.

Wat nu?

Peace,

teste mal:

s.close();```

anstatt ```rs = null;
s = null;```

Perfekt! Funktioniert einwandfrei! Auch der Speicherbedarf nach Programmstart hat sich auf ca. 60MB reduziert (weil die Methode bei Programmstart bereits einmal aufgerufen wird).
Dafür bin ich Dir mindestens ein großes helles Weizen schuldig, wenn Du dich mal in die Nähe von Recklinghausen oder Passau begeben solltest. :wink:

:smiley: wenn ich mal dort bin komm ich darauf zurück :wink:

Snape, bitte korrigiere auch noch die Fehlermeldung. Rechtschreibfehler in Programmen finde ich etwas peinlich.

“Fehler beim Laden der Daten” …

Da das alles in einer Methode passiert, würde ich nicht extra die Variablen außerhalb des try-catch-Blockes initialisieren.
Pack das alles in den try-catch-Block und fange dann eine SQLException und nicht nur eine Exception.

[QUOTE=L-ectron-X]Snape, bitte korrigiere auch noch die Fehlermeldung. Rechtschreibfehler in Programmen finde ich etwas peinlich.

„Fehler beim Laden der Daten“ …
[/QUOTE]
Wenn Du wüsstest, was die schon alles gewohnt sind (nicht von mir/uns). :smiley:
Dagegen ist ein Klein-Großschreibfehler ein Fliegenschiss. :wink:

[QUOTE=L-ectron-X;6294]
Da das alles in einer Methode passiert, würde ich nicht extra die Variablen außerhalb des try-catch-Blockes initialisieren.
Pack das alles in den try-catch-Block und fange dann eine SQLException und nicht nur eine Exception.[/QUOTE]
Worin soll der Vorteil bestehen außer in der Optik? Dass das rs.close() und s.close() in den try-Block kommt ist klar, aber weshalb die Initialisierung?
Ich sehe normalerweise zu, nur so viel wie nötig in den try-Block zu legen, um den Rollback-Teil nicht unnötig aufzublähen und damit unnötig zu verlangsamen.

Ich glaube Lex meint damit ins besondere das man das rollback im catch machen sollte und das close im finally. Das ist nicht nur im Code schöner sondern gehört einfach zum guten Ton/Sourcecode. Und das catchen einer jeden Exception einzeln ist ebenfalls besser zum unterschiedlichen handling der Fehler. Ein Standard-catch von Exception/Throwable ist zusätzlich noch eine Absicherung das dieser Code auch wirklich ordentlich abgearbeitet wird. Denn nicht nur die SQLExceptions sondern auch RuntimeExceptions ala NPE oder ArrayIndexOutOfBounce etc. gehören gefangen und ordentlich behandelt. Spätestens bei einem ordentlichen Logging ist das mehr als wichtig.
Meine Meinung :wink:

Gut Schuß
VuuRWerK :wink: