# Problem mit dem Heap Space (Speicherüberlauf)



## oetzi (6. Sep 2010)

Hallo zusammen,

mir läuft der Heap Space voll, ich komme aber nicht auf die Ursache des Problems.


Ich beschreibe mal was mein Programm macht:
Allgemeine Infos:
- Das Programm analysiert die Logdateien eines Lasttestlaufs und schreibt verschiedene Daten wie IDs und natürlich vorallem die Laufzeit in eine CSV Datei.
- Eine Logdatei ist so aufgebaut, dass sie unter anderem Zeilen wie "Starte Versand..." und "Erhalte Antwort..." neben jede Menge anderen Logeinträgen enthält. 
- Stündlich wird eine neue Logdatei geschrieben, somit teilt sich ein Testlauf auf mehrere Logdateien auf.

Ablauf des Programms:
1. Einlesen der ersten Logdatei. Dabei werden alle Zeilen, die die Schlüsselworte "Starte Versand..." und "Erhalte Antwort..." enthalten in ein String-Array geschrieben.
2. Aufruf einer Methode, die dieses Array auswertet, d. h. aus jeweils einem Start- und dem passenden Antwort-Eintrag wird eine Zeile für die CSV Datei zusammengebastelt.
2.1 Löschen der beiden gerade bearbeiteten Einträge aus dem Array. Dies habe ich gemacht, weil ich dachte, dass dieses Array für den Speicherüberlauf verantwortlich ist. Leider hat das nichts gebracht.
3. Einlesen der nächsten Logdatei, usw.

Ich habe mal eine Ausgabe eingebaut, die nach jeder analysierten Logdatei erzeugt wird und die mir den verbleibenden Speicherplatz anzeigt (Runtime.getRuntime().freeMemory()). Dabei sehe ich, dass jedes mal ca. 60-80Mb flöten gehen. (Die Logdateien sind übrigens ca. 40Mb groß)

Mir fehlt jetzt wohl das tiefere Verständnis vom Speichermanagement von Java. Bis jetzt war ich mit der Info ganz gut bedient, dass der garbage collector den Speicher wieder aufräumt sobald man die Zeiger auf die jeweiligen Objekte entfernt.
Mache ich das nicht, wenn ich per remove die Einträge aus dem Array entferne?

Gibt es sonst noch irgendwelche Java-typischen Speicherfallen in die man reintreten kann?

Wo ich mir noch nicht sicher war ist der bufferedWriter bzw. bufferedReader. Hier habe ich sicherheitshalber ein flush (writer) eingebaut und den reader "close" ich sobald ich ihn nicht mehr benötige. Aber brauchen bufferedWriter/Reader überhaupt viel Speicherplatz?

Sonst fällt mir überhaupt nicht mehr ein, wo mein Speicherfresser versteckt sein könnte.

Hoffe, dass mir da wer einen Tipp geben kann.

Schönen Gruß
oetzi

PS: Habe den Heapspace schon auf max. 1 Gb erhöht. Das reicht aktuell bei weitem nicht.


----------



## ARadauer (6. Sep 2010)

> 1. Einlesen der ersten Logdatei. Dabei werden alle Zeilen, die die Schlüsselworte "Starte Versand..." und "Erhalte Antwort..." enthalten in ein String-Array geschrieben.


wie ließt du ein? Ich würde eine ArrayListe benutzen



> PS: Habe den Heapspace schon auf max. 1 Gb erhöht. Das reicht aktuell bei weitem nicht.


wie hast du das gemacht, bist du dir sicher dass du ein GB zur verfügung hast?


```
System.out.println("Total Memory"+Runtime.getRuntime().totalMemory());    
       System.out.println("Free Memory"+Runtime.getRuntime().freeMemory());
```
was kommt da?


----------



## oetzi (6. Sep 2010)

Hi ARadauer,

danke schonmal für deine Antwort!



ARadauer hat gesagt.:


> wie ließt du ein? Ich würde eine ArrayListe benutzen



Sorry, da habe ich mich nicht klar genug ausgedrückt. Ich benutze eine ArrayList<String>




ARadauer hat gesagt.:


> wie hast du das gemacht, bist du dir sicher dass du ein GB zur verfügung hast?



Ich starte das ganze im Moment aus Eclipse mit den Parametern -Xms512m -Xmx1024m und ja, ich war mir schon recht sicher, dass das Gig zur Verfügung steht... Tut es aber wohl doch nicht; siehe unten



ARadauer hat gesagt.:


> ```
> System.out.println("Total Memory"+Runtime.getRuntime().totalMemory());
> System.out.println("Free Memory"+Runtime.getRuntime().freeMemory());
> ```
> was kommt da?



mhh, die Ausgabe ist interessant!
Die Zeile hier wird wie gesagt nach jeder bearbeiteten Datei ausgegeben:

Free Memory left in Mb: 418 || Total Memory in Mb: 508
Free Memory left in Mb: 335 || Total Memory in Mb: 508
Free Memory left in Mb: 247 || Total Memory in Mb: 508
Free Memory left in Mb: 161 || Total Memory in Mb: 508
Free Memory left in Mb: 97 || Total Memory in Mb: 508
Free Memory left in Mb: 370 || Total Memory in Mb: 508
Free Memory left in Mb: 297 || Total Memory in Mb: 508
Free Memory left in Mb: 203 || Total Memory in Mb: 508
Free Memory left in Mb: 134 || Total Memory in Mb: 508
Free Memory left in Mb: 63 || Total Memory in Mb: 508 <hier nach flog die HeapSpace Exception beim letzten Durchlauf>
Free Memory left in Mb: 378 || Total Memory in Mb: 508
Free Memory left in Mb: 290 || Total Memory in Mb: 508
...

Ich hatte auch schon bevor ich den "total memory" ausgegeben hatte gesehen, dass nach der 5.-6. Datei auf einmal wieder mehr Speicherplatz zur Verfügung steht, hatte allerdings gedacht, dass dies daran liegt, dass einfach die Grenze von 512 (Xms) auf 1024 (Xmx) erhöht wird.
Aber das scheint ja nicht zu stimmen, da der total Memory gleich bleibt.
Desweiteren läuft das Programm jetzt schon länger als beim letzten Mal.
Kann es sein, dass der garbage collector beim letzten Mal einfach zu langsam war? Wenn ich mich richtig erinnere ist es ja mehr oder weniger zufällig, wann der GC wo aktiv ist. (Hatte übrigens auch mal ein System.gc() eingebaut ohne mir wirklich sicher zu sein, dass das wirklich was bringt)

Auf jedenfall schließe ich aus der Ausgabe des Total Memory, dass ich den Xms Wert wohl besser direkt auf 1024 setze, da ja anscheinend nicht mehr Speicher bei Bedarf geholt wird. Korrekt?

PS: Aktuell läuft das Programm immer noch! Der "Free Memory" wurde jetzt schon zum 3. Mal wieder erhöht. Verstehe ich zwar nicht, aber ich will mich nicht beschweren ;-)


----------



## oetzi (6. Sep 2010)

Kleine Ergänzung: 
Die Auswertung ist komplett durch gelaufen.
Das freut mich natürlich auf der einen Seite, auf der anderen Seite befürchte ich ja, dass da vielleicht beim nächsten Lauf wieder die Exception fliegt.

Deswegen wäre ich durchaus noch an Hinweisen interessiert, die mir verraten, warum überhaupt der Speicher aufgebraucht wird. Ist da wirklich einfach der Garbadge Collector zu "langsam" für, oder kann das an was anderem liegen?


----------



## musiKk (6. Sep 2010)

Hast Du mal durch Profiling festgestellt, wo der Speicher verbraucht wird? Eine einfache Variante ist VisualVM, welches einem anzeigen kann, welche Objekte am häufigsten vorkommen.



oetzi hat gesagt.:


> Gibt es sonst noch irgendwelche Java-typischen Speicherfallen in die man reintreten kann?



Ist zwar nur eine Anekdote, aber ich bin mal mit [c]String#substring()[/c] reingefallen. Dabei habe ich von sehr großen Strings nur kleine Teile per [c]substring()[/c] ausgeschnitten. Im Hintergrund wird aber das gleiche char-Array verwendet, sodass der Speicher dadurch gleich bleibt.


----------



## oetzi (6. Sep 2010)

@musiKk: danke für den Tipp. Diese Möglichkeit kannte ich noch gar nicht. Scheint mir aber sehr nützlich zu sein!

Habe jetzt mal mein Programm angeworfen und über VisualVM den Profiler gestartet. 
ein Char-Array verbraucht schicke 99,8% des Speichers. siehe Anhang.

Aber so wirklich bringt mich das jetzt nicht weiter oder?
Da ich nicht mit char-Arrays arbeite, nehme ich an, dass das irgendeine interne Sache von Java ist. Dabei könnte es sich ja offentlichtlich um die ArrayList<String> handeln, die ich hauptsächlich in meinem Programm verwende.


----------



## Woodstock (6. Sep 2010)

Hallo,

wie liest du denn die Datei ein?

Ich würde es mit 'nem BufferedWriter und dann der ReadLine()-Methode machen. Und falls deine Logdateien nicht aus einer einzelnen Zeile bestehen dürften da kleinere Häppchen Speicher ausreichen...

Das ist mir eh ein bisschen unklar, warum du (anscheinend) die ganze Zeit auf einem riesigen char[] 'rumreitest...



> Dabei werden alle Zeilen, die die Schlüsselworte "Starte Versand..." und "Erhalte Antwort..." enthalten in ein String-Array geschrieben.



Ich hoffe, du schreibst "Starte..." und "Erhal..." nicht immer wieder in dein Array?


----------



## Empire Phoenix (6. Sep 2010)

Hm Strings sind intern doch nchts anderes als chararrays ?

fallen mit strings:

"bla" + "blu" +"bli"
erzeugt für jedes + einen Stringbuilder und eine kopie des cahrarrays
lieber wenn mehr als ein + vorkommt gleich eine stringbuilder nehmen.


----------



## oetzi (8. Sep 2010)

so, jetzt komme ich endlich wieder zu diesem Problem hier.

@woodstock: ja ich benutze einen bufferedReader um per readLine die relevanten Zeilen aus den log-Dateien vor zu selektieren und in die ArrayList<String> zu schreiben. Und ja, ich schreibe die komplette Zeile in die ArrayList. 
Wahrscheinlich willst du darauf hinaus, dass das mehr Speicher verbraucht, als wenn ich nur die relevanten Infos per substring extrahieren würde, aber darum geht es ja eigentlich nicht.
Nach der Verarbeitung jeder log-Datei scheinen ja ca. 70Mb Speicher verbraucht zu werden. Diesen Verbrauch könnte ich vielleicht reduzieren, aber das eigentlich Problem wäre damit ja nicht gelöst.
Mir geht es ja darum zu verstehen, warum der belegte Speicher nicht wieder frei gegeben wird, wenn ich per remove die einzelnen Einträge aus der ArrayList wieder lösche.

@Empire Phoenix: 
Habe mich jetzt gerade nochmal bei der Javainsel über das Thema schlau gemacht um das besser nachvollziehen zu können.
okay, wenn ich also 2 String mit dem + Operator verbinde, entsteht ein 3. Stringobjekt mit dem Zielstring, aber auch hier würde ich erwarten, dass dann der Speicherplatz der anderen beiden Strings vom Garbage Collector wieder frei gegeben wird. 
Oder dauert das im Schnitt einfach so lange bis der GC aktiv wird? Werde mir bei Gelegenheit zu dem Thema GC auch mal weitere Literatur gönnen


----------



## musiKk (8. Sep 2010)

oetzi hat gesagt.:


> Werde mir bei Gelegenheit zu dem Thema GC auch mal weitere Literatur gönnen



Die Zeit kannst Du Dir glaube ich sparen. Auf den GC hast Du innerhalb der JVM keinen Einfluss. Klar gibt es [c]System.gc()[/c], aber das ist so sinnvoll wie ein Kropf. Es wird kein Speicherfehler fliegen, bevor der GC nicht probiert hat, Speicher freizugeben.
Der Speicher kann vom GC freigegeben werden, sobald es keine Referenz mehr darauf gibt. Wenn Du also zwei Strings verkettest und einen dritten erhältst, dann wird der Speicher für die ersten beiden so lange nicht freigegeben, wie sie noch referenziert werden.


----------



## ?!?!? (8. Sep 2010)

> okay, wenn ich also 2 String mit dem + Operator verbinde, entsteht ein 3. Stringobjekt mit dem Zielstring, aber auch hier würde ich erwarten, dass dann der Speicherplatz der anderen beiden Strings vom Garbage Collector wieder frei gegeben wird.



Ohne dem Garbage Collector explicit zu sagen welcher String zum entfernen bereit ist, ist es unbestimmt wann diese entfernt werden.
Normalerweise sagt man den Garbage Collector bitte die zwei Strings entfernen: 


```
stringEins = null;
stringZwei = null;
```
Um der VM mehr Speicher zur Verfügung zu stellen macht man eigentlich:


```
String userdir = System.getProperty("user.dir");
        String cmd = "java -Xms512m -Xmx1024m -jar \"" + userdir + "\\App.jar\"";
        try {
            Runtime.getRuntime().exec(cmd);
        } catch (IOException e) {
            // Mach was
        }
        System.exit(0);
```

Und das sollte eigentlich keine Probleme machen.


----------



## oetzi (8. Sep 2010)

danke für die Infos. 
Auch wenn das Thema noch nicht restlos geklärt ist, werde ich es mal als erledigt markieren.
Sonst kann man sich ja noch tagelang darüber unterhalten


----------



## DerBesucher (17. Sep 2010)

java hat für Strings einen  speziellen Speicherbereich der nicht vom GC überwacht wird. einfach mal googlen


----------

