java.lang.OutOfMemoryError in Schleifenkonstrukt

Johannes7146

Goldschnabel
In folgendem Code läuft mit jedesmal der Heap Space voll, egal ob ich mit 64,128 oder 256mb die VM starte.

Auch ist der Punkt an dem das ganze Auftritt nicht immer genau an der selben Stelle.
Ich habe keinerlei Streams offen. Ich vermute Hibernate ist schuld daran.
habe an vielen Stellen auch schon die Session gecleart, leider ohne Erfolg.
Evtl kann jemand von euch mal einen Blick drauf werden.

Danke
Java:
for (int aktMaschine = startMaschine; aktMaschine < maschinen.size(); aktMaschine++) {
            log.info("Datenarchivierung_archiviere--> " + "Maschine " + (aktMaschine + 1)
                    + " von " + maschinen.size() + " -->"
                    + maschinen.get(aktMaschine).getSeriennummer());
            /* Durchschnittzeiten und Durchläufe neu anlegen */
            if (timeAverage == null) {
                timeAverage = new HashMap<String, Double>();
                for (String typ : messungstypen) {
                    timeAverage.put(typ, 0.0);
                }
            }
            if (durchlaeufe == null) {
                durchlaeufe = new HashMap<String, Integer>();
                for (String typ : messungstypen) {
                    durchlaeufe.put(typ, 0);
                }
            }
            //-----------------------------------------------------------
            List<String> locales = getLocalesVonMaschine(maschinen.get(aktMaschine));
            //-----------------------------------------------------------
            /* Schleife über alle Messungtypen */
            for (int aktMessungsTyp = startMessungstyp; aktMessungsTyp < messungstypen
                    .size(); aktMessungsTyp++) {

                if (messungstypen.get(aktMessungsTyp)
                        .equals(Messungstyp.SCHLAGMONITORING)) {
                    //Aufgrund der Komprimierung kann dieses Profil übersprungen werden
                    continue;
                }

                /*
                 * Erntetage holen
                 */
                List<Date> erntetage = ErntetagService.getErntetage(maschinen
                        .get(aktMaschine).getMaschineId(), new String[] { messungstypen
                        .get(aktMessungsTyp) }, von.getTime(), bis.getTime(), null);
                long profilstart = System.currentTimeMillis();

                /*
                 * Schleife über alle Erntetage
                 */
                for (int aktTag = startTag; aktTag < erntetage.size(); aktTag++) {
                    collection = new ArrayList<Maschine>();
                    collection.add(maschinen.get(aktMaschine));
                    GregorianCalendar vonMitStdAngabe = new GregorianCalendar();
                    vonMitStdAngabe.setTime(erntetage.get(aktTag));
                    vonMitStdAngabe.set(GregorianCalendar.HOUR_OF_DAY,
                                        Kalender.ERNTETAG_BEGINN);

                    GregorianCalendar bismitStdAngabe = (GregorianCalendar) vonMitStdAngabe
                            .clone();
                    bismitStdAngabe.add(GregorianCalendar.DAY_OF_MONTH, 1);

                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy\\MM\\dd");
                    long start = System.currentTimeMillis();

                    for (String land : locales) {

                        Locale loc = new Locale(land);

                        String save_path = rootDatenarchivierung + "\\csv" + "\\"
                                + maschinen.get(aktMaschine).getSeriennummer() + "\\"
                                + sdf.format(vonMitStdAngabe.getTime()) + "\\"
                                + land.toLowerCase() + "\\";

                        if (messungstypen.get(aktMessungsTyp)
                                .equalsIgnoreCase(Messungstyp.ALARMMELDUNG)) {

                            CSVAlarmExporter export = new CSVAlarmExporter(maschinen
                                                                                   .get(aktMaschine),
                                                                           vonMitStdAngabe
                                                                                   .getTime(),
                                                                           bismitStdAngabe
                                                                                   .getTime(),
                                                                           save_path,
                                                                           "Alarm-CSV.csv",
                                                                           loc);

                            /* CVS-File erstellen */
                            log.info("ArchivierenThread_archiviere-->Erstelle CSV für: "
                                    + maschinen.get(aktMaschine).getSeriennummer() + " "
                                    + messungstypen.get(aktMessungsTyp) + " "
                                    + erntetage.get(aktTag));

                            export.createCSVExport();
                        } else {

                            CSVExportNew export = new CSVExportNew(collection,
                                                                   vonMitStdAngabe
                                                                           .getTime(),
                                                                   bismitStdAngabe
                                                                           .getTime(),
                                                                   messungstypen
                                                                           .get(aktMessungsTyp),
                                                                   loc,
                                                                   true,
                                                                   save_path);

                            /* CVS-File erstellen */
                            log.info("ArchivierenThread_archiviere-->Erstelle CSV für: "
                                    + maschinen.get(aktMaschine).getSeriennummer() + " "
                                    + messungstypen.get(aktMessungsTyp) + " "
                                    + erntetage.get(aktTag));
                            export.buildCSVFile();
                        }
                        //ENDE schleife über Locales
                        HibernateSessionFactory.clearSession();
                    }

                    long stop = System.currentTimeMillis();

                    /* Durchläufe hochzählen */
                    durchlaeufe.put(messungstypen.get(aktMessungsTyp), durchlaeufe
                            .get(messungstypen.get(aktMessungsTyp)) + 1);

                    /* Neuen Durchschnittswert(Dauer) speichern */
                    timeAverage.put(messungstypen.get(aktMessungsTyp),
                                    zeitberechnung(start,
                                                   stop,
                                                   timeAverage.get(messungstypen
                                                           .get(aktMessungsTyp)),
                                                   durchlaeufe.get(messungstypen
                                                           .get(aktMessungsTyp))));
                    if (log.isEnabledFor(org.apache.log4j.Level.DEBUG)) {
                        StringBuffer sb = new StringBuffer();
                        sb.append("Von         : " + vonMitStdAngabe.getTime() + "\n");
                        sb.append("Bis         : " + bismitStdAngabe.getTime() + "\n");
                        sb.append("Maschine    : "
                                + maschinen.get(aktMaschine).getSeriennummer() + "\n");
                        sb.append("Messungstyp : " + messungstypen.get(aktMessungsTyp)
                                + "\n");
                        sb.append("Dauer       : " + ((stop - start) / 1000) + " sec"
                                + "\n");
                        sb.append("Durschlauf  : "
                                + durchlaeufe.get(messungstypen.get(aktMessungsTyp))
                                + " von " + erntetage.size() + "\n");
                        sb.append("Durschnitt  : "
                                + timeAverage.get(messungstypen.get(aktMessungsTyp))
                                + " sec\n" + "\n");
                        log.debug("Datenarchivierung_archiviere-->\n" + sb.toString());
                        sb = null;
                    }
                    /*
                     * Prüfen ob heruntergefahren werden soll.
                     */
                    if (!isRunning()) {
                        log
                                .info("ArchivierenThread_archiviere-->Archivierung wird unterbrochen und zwischengespeichert");
                        // aktuellen Tag hochzählen damit nicht der letzte
                        // nochmal durchaufen wird
                        aktTag++;

                        /*
                         * Wenn aktTag == erntetage.size() dann sind alle Tage durchlaufen und
                         * der aktuelle Tag muss auf 0 gesetzt werden. Außerdem muss der
                         * nächstem Durchlauf mit dem nächsten Profil fortgesetzt werden
                         */
                        if (aktTag == erntetage.size()) {
                            aktTag = 0;
                            aktMessungsTyp++;
                            /*
                             * Wenn aktMessungsTyp == messungstypen.size() dann sind alle
                             * Profile für diese Maschine durchlaufen. Das beudetet der nächste
                             * Durchlauf mit dem erstem Profil und der nächsten Maschine
                             * fortgesetzt werden muss
                             */
                            if (aktMessungsTyp == messungstypen.size()) {
                                aktMessungsTyp = 0;
                                aktMaschine++;

                                durchlaeufe = null;
                                timeAverage = null;
                            }
                        }

                        /*
                         * Alle notwendigen Parameter werden in einer
                         * DatenarchivierungUnterbrechnung gespeichert
                         */
                        DatenarchivierungUnterbrechnung dau = new DatenarchivierungUnterbrechnung();
                        dau.setThread("archivieren");
                        dau.setAktTag(aktTag);
                        dau.setAktMaschine(aktMaschine);
                        dau.setAktMessungsTyp(aktMessungsTyp);
                        dau.setBis(bis);
                        dau.setVon(von);
                        dau.setErntetage(erntetage);
                        dau.setMaschinen(maschinen);
                        dau.setMessungstypen(messungstypen);
                        dau.setDurchlaeufe(durchlaeufe);
                        dau.setTimeAverage(timeAverage);
                        /* Objekt< serialisieren */
                        dam.objektSpeichern(dau);
                        bereitsGespeichert = true;
                        log
                                .info("ArchivierenThread_archiviere-->Archivierung wurde unterbrochen und zwischengespeichert");
                        HibernateSessionFactory.clearSession();
                        return false;
                    }
                    HibernateSessionFactory.clearSession();
                }// ENDE: Schleife über Erntetage

                if (erntetage.size() > 0) {
                    long profiende = System.currentTimeMillis();
                    long dauer = profiende - profilstart;
                    dauer /= 1000;
                    long sec = dauer % 60;
                    dauer -= sec;
                    long min = dauer / 60;
                    if (log.isEnabledFor(org.apache.log4j.Level.DEBUG)) {
                        log.debug("Datenarchivierung_archiviere-->" + "Für das Profil "
                                + messungstypen.get(aktMessungsTyp)
                                + " wurde folgende Zeit benötigt: " + min + " Minuten "
                                + sec + " Sekunden");
                    }

                } else {
                    if (log.isEnabledFor(org.apache.log4j.Level.DEBUG)) {
                        log.debug("Datenarchivierung_archiviere-->\n" + "Für das Profil "
                                + messungstypen.get(aktMessungsTyp)
                                + " wurden keine Messungen gefunden");
                    }
                }
                HibernateSessionFactory.clearSession();
            }// ENDE: Schleife über Messungstypen

            /*
             * Anzahl der Durchläufe und Durchschnitte wieder auf 0 setzen
             */
            timeAverage = null;
            durchlaeufe = null;
            //
            timeAverage = new HashMap<String, Double>();
            for (String typ : messungstypen) {
                timeAverage.put(typ, 0.0);
            }
            durchlaeufe = new HashMap<String, Integer>();
            for (String typ : messungstypen) {
                durchlaeufe.put(typ, 0);
            }
            //
        }// ENDE: Schleife über alle Maschinen
 
hast du die for schleifen mal gedebugt?
Könnte ja sein das sie in ner endlos schleife laufen, und wenn dan zu viele Objekte instanziert werden, haste deinen Heapspace.

Gruß DeviAn
 
Hallo Johannes,

was in dem Code sind denn deine Entities? Könnte es sein, dass Du zyklische Abhängigkeiten hast? Ich könnte mir vorstellen, dass das im Zusammenspiel mit Hibernate unvorhersehbar endet. Das Eclipse Plugin Findbugs(1) hilft dabei so etwas herauszufinden.

Würdest Du denn ein paar Stacktrace posten?

Gruß Sen

(1) http://findbugs.cs.umd.edu/eclipse/
 
Findbugs habe ich bereits, gibt aber keine Meldung.
Habe gerade keinen Stacktrace, kann ihn aber nochmal anmachen.. dauert aber nen Moment bis das Problem wieder auftritt.

zyklische Abhängigkeiten der Entites kann ich denke ich ausschließen. Das ganze ist teil eines ziemlich großen Projektes, welches schon seit einigen Jahren läuft. An der Hibernate config und dem ER-Modell habe ich ncihts geändert.

Edit:

@devian_der_9999999 ich sehe anhand meiner ausgaben der er Maschine für Maschine abarbeitet und nicht irgendwo hängen bleibt.
 
Hallo,

an deiner Stelle würde ich jeder Iteration über ernteTage die "collection" explizit mit clear() leeren. Ebenso alle Weiteren Collections deren Werte man nach dem entsprechenden Abschnitt nicht braucht. Weiterhin solltest du niemals solche Monster Methoden schreiben. Zerlege den Ablauf in einzelne Blöcke und ruf dann in einer Methode die entsprechenden Blöcke nacheinander auf. So würde man einfacher sehen, was da auf high-level ebene passiert.

Gruß Tom
 
Abgesehen von deinem java.lang.OutOfMemoryError fallen mir noch ein paar Performancebremsen in deinem Code auf:

timeAverage und durchlaeufe wird am Anfang und am Ende der Schleife neu initialisiert. Nur am Anfang reicht aus. Dabei reicht es wenn du nur einmal über messungstypen läufst und beide Maps gleichzeitig initialisierst.

Du wiederholt über einen Index auf deine Listen zu
maschinen.get(aktMaschine)
Indizierte Zugriffe auf Listen sind zwar schnell, aber es ist doch schöner den wiederholten Zugriff zu vermeiden in dem du das Objekt am Anfang der Schleifendurchläufe jeweils nur einmal abrufst und dann damit arbeitest.

Ein SimpleDateFormat ist auch nicht gerade das schnellste. Lege es ganz am Anfang deiner Methode nur einmal an.

Werden in deinem Export die Filestreams geschlossen?

Ist dein dam.objektSpeichern(dau); ein ObjectOutputStream? Ein ähnliches Problem mit OutOfMemory gab es gestern. Schau mal ob ein Aufruf von reset nach dem speichern eines Objektes das Problem behebt.

Zum umrechnen von Zeiten kannst du (sofern deine Java-Version das schon hat) den enum TimeUnit verwenden. Das ganz wird dann deutlich lesbarer.
 
In dem anderem Thema habe ich auch mitgelesen (und geschrieben) dam und dau sind keine Streams. das Speichern der CSV dateien (die maximal 100kb groß sind) ist eine bereits vorhandene klasse die seit 2 jahren im einsatz ist. Das dort irgendetwas falsch läuft mag ich bezweifeln.

Vielen dank (auch an Thomas) bezüglich der Hinweise auf den Programmierstil.
Ich bin im 2. Ausbildungsjahr und bin vorher mit 0 Programmierkenntnissen eingestiegen. Ich werde es darauf hin ändern und den Code gleich nochmals posten.

Ich zweifle allerdings daran, dass dadruch der Heap space nun nichtmehr vollaufen wird.
Performance wird sicherlich dadruch steigen (wobei es in diese Anwendung nicht unbedingt daruaf ankommt).
 
Übermäßiges Performanceoptimieren ist ja auch gar nicht so gut und meist auch nicht nötig. Aber auf diese einfachen Sachen kann man achten :)

Zu deinem OutOfMemory fällt mir sonst nichts mehr ein. Du könntest dir mal nen Profiler installieren (vom JProfiler eine Testversion z.B.) und dann mal schauen was da so viel Speicher verbraucht. Am Code kann ich momentan nichts ablesen.
 
Meine Schleife von eben sieht nun so aus:
Java:
for (int aktMaschine = startMaschine; aktMaschine < maschinen.size(); aktMaschine++) {
            log.info("Datenarchivierung_archiviere--> " + "Maschine " + (aktMaschine + 1)
                    + " von " + maschinen.size() + " -->"
                    + maschinen.get(aktMaschine).getSeriennummer());

            Maschine maschine = maschinen.get(aktMaschine);

            boolean weitermachen = durchlaufMaschine(maschine,
                                                     startMessungstyp,
                                                     messungstypen,
                                                     startTag,
                                                     aktMaschine);
            if (!weitermachen) {
                return true;

            }

        }// ENDE: Schleife über alle Maschinen


Java:
private boolean durchlaufMaschine(Maschine maschine,
                                      int startMessungstyp,
                                      List<String> messungstypen,
                                      int startTag,
                                      int aktMaschine) {
        List<String> locales = getLocalesVonMaschine(maschine);

        /* Schleife über alle Messungtypen */
        for (int aktMessungsTyp = startMessungstyp; aktMessungsTyp < messungstypen.size(); aktMessungsTyp++) {

            String messungstyp = messungstypen.get(aktMessungsTyp);

            boolean weitermachen = durchlaufMessungstyp(maschine,
                                                        messungstyp,
                                                        startTag,
                                                        locales,
                                                        aktMaschine,
                                                        aktMessungsTyp,
                                                        messungstypen);
            if (!weitermachen) {
                return false;
            }
        }
        return true;

    }


Java:
 private boolean durchlaufMessungstyp(Maschine maschine,
                                         String messungstyp,
                                         int startTag,
                                         List<String> locales,
                                         int aktMaschine,
                                         int aktMessungsTyp,
                                         List<String> messungstypen) {
        if (messungstyp.equals(Messungstyp.SCHLAGMONITORING)) {
            //Aufgrund der Komprimierung kann dieses Profil übersprungen werden

        } else {

            /*
             * Erntetage holen
             */
            List<Date> erntetage = ErntetagService
                    .getErntetage(maschine.getMaschineId(),
                                  new String[] { messungstyp },
                                  von.getTime(),
                                  bis.getTime(),
                                  null);
            /*
             * Schleife über alle Erntetage
             */
            for (int aktTag = startTag; aktTag < erntetage.size(); aktTag++) {

                Date ernteTag = erntetage.get(aktTag);

                boolean weitermachen = durchlaufErnteTag(maschine,
                                                         ernteTag,
                                                         locales,
                                                         messungstyp,
                                                         aktMaschine,
                                                         aktTag,
                                                         aktMessungsTyp,
                                                         erntetage,
                                                         messungstypen);
                if (!weitermachen) {
                    return false;
                }

                HibernateSessionFactory.clearSession();
            }// ENDE: Schleife über Erntetage

            HibernateSessionFactory.clearSession();
        }// ENDE: Schleife über Messungstypen
        return true;
    }


Java:
private boolean durchlaufErnteTag(Maschine maschine,
                                      Date ernteTag,
                                      List<String> locales,
                                      String messungstyp,
                                      int aktMaschine,
                                      int aktTag,
                                      int aktMessungsTyp,
                                      List<Date> erntetage,
                                      List<String> messungstypen) {
        List<Maschine> collection = new ArrayList<Maschine>();
        collection.add(maschine);
        GregorianCalendar vonMitStdAngabe = new GregorianCalendar();
        vonMitStdAngabe.setTime(ernteTag);
        vonMitStdAngabe.set(GregorianCalendar.HOUR_OF_DAY, Kalender.ERNTETAG_BEGINN);

        GregorianCalendar bismitStdAngabe = (GregorianCalendar) vonMitStdAngabe.clone();
        bismitStdAngabe.add(GregorianCalendar.DAY_OF_MONTH, 1);

        for (String land : locales) {

            Locale loc = new Locale(land);

            String save_path = rootDatenarchivierung + "\\csv" + "\\"
                    + maschine.getSeriennummer() + "\\"
                    + sdf.format(vonMitStdAngabe.getTime()) + "\\" + land.toLowerCase()
                    + "\\";

            if (messungstyp.equalsIgnoreCase(Messungstyp.ALARMMELDUNG)) {

                CSVAlarmExporter export = new CSVAlarmExporter(maschine,
                                                               vonMitStdAngabe.getTime(),
                                                               bismitStdAngabe.getTime(),
                                                               save_path,
                                                               "Alarm-CSV.csv",
                                                               loc);

                /* CVS-File erstellen */
                log
                        .info("ArchivierenThread_archiviere-->Erstelle CSV für: "
                                + maschine.getSeriennummer() + " " + messungstyp + " "
                                + ernteTag);

                export.createCSVExport();
            } else {

                CSVExportNew export = new CSVExportNew(collection,
                                                       vonMitStdAngabe.getTime(),
                                                       bismitStdAngabe.getTime(),
                                                       messungstyp,
                                                       loc,
                                                       true,
                                                       save_path);

                /* CVS-File erstellen */
                log
                        .info("ArchivierenThread_archiviere-->Erstelle CSV für: "
                                + maschine.getSeriennummer() + " " + messungstyp + " "
                                + ernteTag);
                export.buildCSVFile();
            }
            //ENDE schleife über Locales
            HibernateSessionFactory.clearSession();
        }

        /*
         * Prüfen ob heruntergefahren werden soll.
         */
        if (!isRunning()) {
            log
                    .info("ArchivierenThread_archiviere-->Archivierung wird unterbrochen und zwischengespeichert");
            // aktuellen Tag hochzählen damit nicht der letzte
            // nochmal durchaufen wird
            aktTag++;

            /*
             * Wenn aktTag == erntetage.size() dann sind alle Tage durchlaufen und der aktuelle
             * Tag muss auf 0 gesetzt werden. Außerdem muss der nächstem Durchlauf mit dem
             * nächsten Profil fortgesetzt werden
             */
            if (aktTag == erntetage.size()) {
                aktTag = 0;
                aktMessungsTyp++;
                /*
                 * Wenn aktMessungsTyp == messungstypen.size() dann sind alle Profile für diese
                 * Maschine durchlaufen. Das beudetet der nächste Durchlauf mit dem erstem
                 * Profil und der nächsten Maschine fortgesetzt werden muss
                 */
                if (aktMessungsTyp == messungstypen.size()) {
                    aktMessungsTyp = 0;
                    aktMaschine++;
                }
            }

            /*
             * Alle notwendigen Parameter werden in einer DatenarchivierungUnterbrechnung
             * gespeichert
             */
            DatenarchivierungUnterbrechnung dau = new DatenarchivierungUnterbrechnung();
            dau.setThread("archivieren");
            dau.setAktTag(aktTag);
            dau.setAktMaschine(aktMaschine);
            dau.setAktMessungsTyp(aktMessungsTyp);
            dau.setBis(bis);
            dau.setVon(von);
            dau.setErntetage(erntetage);
            dau.setMaschinen(maschinen);
            dau.setMessungstypen(messungstypen);
            /* Objekt< serialisieren */
            dam.objektSpeichern(dau);
            bereitsGespeichert = true;
            log
                    .info("ArchivierenThread_archiviere-->Archivierung wurde unterbrochen und zwischengespeichert");
            HibernateSessionFactory.clearSession();
            return false;
        }
        return true;
    }
 
So ist es doch schonmal übersichtlicher :)

Aber noch zwei Anmerkungen:
In der Methode durchlaufErnteTag willst du in bestimmten Fällen die Indizes der äußeren Schleifen erhöhen. Da ein int aber ein primitiv und kein Objekt ist, kommt dies nicht bei deinen Schleifen an. Da mußt du dir was anderes einfallen lassen.

Achte beim zusammenbauen von Pfadangaben darauf, dass du Plattformunabhängig bleibst. Statt \\ solltest du daher File.separator verwenden.
 

Neue Beiträge

Zurück