# Speicherprobleme (OutOfMemory Error)



## Rock Lobster (14. Aug 2007)

Servus,

habe im Moment mit einigen ernsten Speicherproblemen zu kämpfen, und ich habe keine Ahnung, wie ich das vernünftig in den Griff kriegen soll.

Ein Beispiel:
Mein Programm bekommt 1024 MByte Speicher zugewiesen. Es lädt beim Start einige sehr große WAV-Dateien. Jetzt ist es aber so, daß das Programm es manchmal "schafft", und benötigt dann laut Taskmanager um die 960 MByte. Aber wenn ich es danach nochmal starte, kann es sein, daß es knapp 1200 MByte benötigt und sich dann mit einem OutOfMemory-Error verabschiedet, und das, obwohl es in beiden Fällen haargenau das gleiche macht.

Das ist schonmal das erste, und da sehe ich keine vernünftige Chance, das irgendwie zu beheben (wenn man mal davon absieht, daß man den zugewiesenen Speicher natürlich noch vergrößern könnte, logisch).

Das nächste ist, daß ich vor dem Einlesen einer Datei prüfen will, ob überhaupt noch Speicher zur Verfügung steht. Hier benutze ich Runtime.getRuntime().freeMemory(), aber die zeigt leider oft völlig unrealistische Werte an. Beispielsweise sagt sie, es sind noch 13 MByte frei, obwohl zu dem Zeitpunkt noch mehrere hundert hätten frei sein müssen.

Gibt es ein paar gute Strategien, um mit Speicherproblemen umgehen zu können? Gibt es todsichere Möglichkeiten, einen OutOfMemory-Error rechtzeitig und sicher zu verhindern?
Ich hoffe, ihr könnt mir helfen...


----------



## SlaterB (14. Aug 2007)

zu einem Teilpunkt:

> Beispielsweise sagt sie, es sind noch 13 MByte frei, obwohl zu dem Zeitpunkt noch mehrere hundert hätten frei sein müssen. 

weil noch nicht alles allokiert wurde,
sinnvoller ist die Größe, wieviel aktuell belegt ist, wenn du dann das Maximum kennst, weißt du, wieviel noch möglich ist,

ob du die Max-Größe irgendwo komfortabel nachlesen kannst weiß ich nicht,
dürfte wohl eine System-Property sein, wenn du sie beim Start mitangibst,
oder zunächst mal fest auf 1024 MB setzen


----------



## MarcoBehnke (14. Aug 2007)

getFreeMemory zeigt Dir nur an, wieviel Speicher aktuell verfügbar ist, d.h. aber nicht, dass nicht doch noch weitere Speicher für das Programm zur Verfügung gestellt werden kann. Du kannst über die VM Parameter MIN und MAX Mem steuern. Beides auf den gleichen Wert setzen, bedeutet sofort alles zur Verfügung zu stellen.

Grundsätzlich stellt sich doch grad die Frage, warum Du überhaupt so viel auf einmal laden musst? Kannst Du das nicht Streamen/Buffern?

Erzähl mal etwas über den Anwendungsfall. OutOfMemory kannst Du leider nicht abfangen.


----------



## The_S (14. Aug 2007)

Ich kenn mich jetzt mit Runtime nicht so aus, aber evtl. liefert dir getFreeMemory die höchste, noch am Stück verfügbare, Speichermenge zurück!?

Du benötigst doch bestimmt nicht alle WAV's gleichzetig oder? Dann lade doch immer nur die WAV, die momentan auch wirklich benötigt wird!


----------



## Rock Lobster (14. Aug 2007)

Naja es ist so:

Das ganze ist ein Audioplayer, der mit unterschiedlichen Strategien auf die WAV-Files zugreifen soll. Zunächst mal versucht er es mit RandomAccessFile. Wenn das nicht geht, lädt er das File als Tempfile in den Temp-Ordner und greift dann auch mit RandomAccessFile zu. Je nach Anwendungszweck können Tempfiles aber auch verboten sein, in dem Fall bleibt ihm nichts übrig als das File komplett in den RAM zu laden. Streams sind für meinen Zweck zu langsam (da auch die Bilder gerendert werden usw), deswegen MUSS das Teil in den RAM. Und erst wenn DAS auch nicht mehr hinhaut, wird als letzte Notlösung ein Stream benutzt.

So und um nun abzuprüfen, ob es überhaupt noch in den RAM paßt, brauche ich eben eine Lösung. Im Moment ist es aber so, daß einfach der Fehler auftritt und vorbei ist es. Ich habe also so gesehen gar nichterst die Möglichkeit, auf die Streams auszuweichen (weil er den OutOfMemory nicht vorhersagen kann und es eher "drauf ankommen läßt" und dann abkackt).

Daher muß ich irgendwie prüfen können, wie viel Speicher mir noch zur Verfügung steht. Leider geht das nicht, weil freeMemory() ungenaues liefert. Und usedMemory() oder so gibt's ja auch nicht. Ich kann zwar maxMemory() erfragen, aber das bringt mir nix, wenn ich den keinen usedMemory() kriegen kann.


----------



## Rock Lobster (14. Aug 2007)

@ Hobbit: Leider kommen in meinem Anwendungsfall auch WAV-Files vor, die sehr groß sind. Zwar ist das eher eine Ausnahme, aber das Teil ist keine Consumer-Anwendung sondern eher industriell, und dort sind große Datenmengen keine Seltenheit. Daher kann auch schon ein einziges WAV, das geladen wird, zu viel Speicher benötigen...


----------



## MarcoBehnke (14. Aug 2007)

und der Audioplayer spielt mehrere Waves auf einmal ab oder nur eines zur Zeit?


----------



## The_S (14. Aug 2007)

Rock Lobster hat gesagt.:
			
		

> @ Hobbit: Leider kommen in meinem Anwendungsfall auch WAV-Files vor, die sehr groß sind. Zwar ist das eher eine Ausnahme, aber das Teil ist keine Consumer-Anwendung sondern eher industriell, und dort sind große Datenmengen keine Seltenheit. Daher kann auch schon ein einziges WAV, das geladen wird, zu viel Speicher benötigen...



Wenn du WAVs Jenseits von 1 GB Speicher laden musst, und diese komplett im RAM halten willst, dann musst du so uns so den heap erhöhen ...


----------



## SlaterB (14. Aug 2007)

Runtime runtime = Runtime.getRuntime();
        long max = runtime.maxMemory();
        long free = runtime.freeMemory();
        long total = runtime.totalMemory();

total ist der aktuelle belegte Speicher, äh, total - free

bei max dürften doch sogar die 1024 rauskommen?
also max - total + free für aktuell noch freien Speicher


----------



## Rock Lobster (14. Aug 2007)

@ MarcoBehnke: Auch das kommt vor.

@ Hobbit: Will ich ja eben nicht. In solch einem Fall soll das Programm merken, daß das nicht geht, und auf einen Stream zurückgreifen (was die letzte Wahl sein soll). Und genau das ist ja mein Problem.

@ SlaterB: Leider liefert mir freeMemory() falsche Ergebnisse, zumindest wollte ich abprüfen, ob der freeMemory() eine bestimmte Grenze unterschreitet. Wenn ja, sollte er NICHT anfangen, irgendwas in den Speicher zu laden. Aber schon kommt der OutOfMemory-Error... und ich hab leider auch irgendwo gelesen, daß freeMemory() nur schätzt 

Bei maxMemory() kommen bei mir 1016 raus. Ich nehme an, daß die VM den Rest belegt oder so.


----------



## Kim Stebel (14. Aug 2007)

```
System.gc();
try
{
  //dein code
}
catch (OutOfMemoryError e)
{
  //pech gehabt..
}
```


----------



## SlaterB (14. Aug 2007)

@Rock Lobster
dass nur geschätzt wird, habe ich bisher nicht beobachtet (bewege mich aber nicht in den Dimensionen  ),
auf jeden Fall solltest du eine Sicherheitsreserve einplanen:
wenn deine 900 MB-Datei auch mal 1.2 GB belegt,
dann sage einfach: Dateien bis 500 MB passen in meine 1024 MB Speicher,
größere Dateien müssen gestreamt werden,

falls übersehen lies noch mal mal letztes (editiertes) Post zur Speicherberechnung,
 max - total + free


----------



## Rock Lobster (14. Aug 2007)

OutOfMemory catchen hab ich auch schon versucht, aber in dem Moment ist es bereits zu spät. Das Programm läuft dann nicht mehr richtig. Eigentlich will ich gern vorher abfragen können, ob es zu einem OutOfMemory-Error kommen wird.


----------



## MarcoBehnke (14. Aug 2007)

Das Ding kommt aus der VM
http://java.sun.com/j2se/1.4.2/docs/api/java/lang/OutOfMemoryError.html

Meines Wissens nach kann das nicht gecatched werden. Wenn OutOfMemory, dann OutOfMemory, da gibts auch kein zurück mehr.

Extended ja auch von Error und nicht von Exception....


----------



## MarcoBehnke (14. Aug 2007)

> public class Error
> extends Throwable
> 
> An Error is a subclass of Throwable that indicates serious problems* that a reasonable application should not try to catch*. Most such errors are abnormal conditions. The ThreadDeath error, though a "normal" condition, is also a subclass of Error because most applications should not try to catch it.
> ...


----------



## SlaterB (14. Aug 2007)

> Meines Wissens nach kann das nicht gecatched werden. 

wobei aber catch(Error e) oder gar catch(Throwable t) zumindest technisch möglich ist


----------



## MarcoBehnke (14. Aug 2007)

ja, aber nicht gemacht wird.



> Thrown to indicate that the *Java Virtual Machine is broken* or has run out of resources necessary for it to continue operating.


----------



## Rock Lobster (14. Aug 2007)

Jepp sag ich ja... da ist alles zu spät 

Habe mir jetzt immer den free Memory ausgeben lassen nach Slater's Methode, und jetzt kackt er nie ab. Das ist auch etwas blöd  naja ich test jetzt mal weiter und hoffe daß er nochmal abkackt...


----------



## Rock Lobster (14. Aug 2007)

AHHHH okay ich weiß nun auch warum.

Ich vergleiche nämlich bereits mit Slater's "free" und nicht mit reinem freeMemory(), und das hat bei mir bereits bisher den "jetzt ist nicht mehr genug Speicher da"-Error ausgelöst, fälschlicherweise. Danach fängt er an, den Stream zu öffnen (anstatt das Ding in den RAM zu laden), und DORT fliegt dann ein OutOfMemory-Error, weil er beim Streamen ja auch einen Buffer anlegt...


----------



## Rock Lobster (14. Aug 2007)

Also mein Problem hat sich jetzt folgendermaßen verschoben:

Zwar scheint Slater's Methode, um den freien Speicher zu berechnen, zu funktionieren. Aber ich muß ja nun mit irgendwas vergleichen, idealerweise mit der Größe der Datei. Den Dateinamen kriege ich aber über eine URL, und da kann ich leider nicht immer eine File()-Objekt draus machen, um die Dateigröße rauszukriegen.

Also war mein Plan, einfach rechtzeitig aufzuhören, z.B. wenn "free" kleiner als 16 oder 32 MByte ist. Aber selbst dann ist es zu spät, denn er liest dann zwar nicht in den RAM sondern versucht, einen Stream zu öffnen, aber der BufferedInputStream legt ja intern auch Buffer an, und hier fliegt dann der OutOfMemory-Error.

Was ich hierbei nicht verstehe: ich glaube kaum, daß der BufferedInputStream intern mit einem Buffer von 32 MByte Größe arbeitet!?

Weiß jemand, woran das nun wieder liegen kann? Eigentlich muß doch noch genug Platz sein! Ich verwerfe sogar das zuvor eingelesene (mit flush() und null-Zuweisung) und rufe anschließend runFinalization() und gc() auf. Ich weiß, das ist keine Garantie, aber selbst wenn er's nicht macht, müßte noch genug Platz für einen InputStream sein...


EDIT: Okay, ich sehe nun, daß der gleiche Fehler auch dann kommt, wenn ich gar nicht erst in den RAM lese, sondern DIREKT als Stream öffne. Scheinbar will der Stream gleich mal viel Platz beanspruchen. Also ist der Stream schuld. Ich benutze einen BufferedInputStream, weil ich markSupported = true benötige. Gibt es Alternativen, oder ist meine Handhabung vielleicht falsch? Bzw. hatte jemand schon ähnliche Probleme, was den Stream angeht?


----------



## SlaterB (14. Aug 2007)

bisher war davon die Rede, dass du die ganze Datei im Speicher haben willst,
nun sind dir schon 32 MB Buffer zu groß,..
wie denn nun?

und schreibe vielleicht mal ein 50-Zeilen-Testprogramm, das eine Datei von der Festplatte einliest, 
dann kann der geneigte Helfer vielleicht mal nachschauen, was da speichermäßig passiert


----------



## Rock Lobster (14. Aug 2007)

Ich hab vorhin doch ganz genau geschrieben, wie ich vorgehe. Und daß ich einen Stream öffne, wenn die Datei NICHT in den Speicher paßt. Mittlerweile habe ich eine Möglichkeit, rechtzeitig vorher abzubrechen und kann nun zum Stream übergehen. Aber da liegt ebenfalls ein Speicherproblem vor.

Ist es denn schlimm, wenn sich von zwei Problemen eines löst, bzw. daß ein zweites Problem durch die Lösung des ersten erst deutlich wird?!

Nochmal:
- Datei soll in RAM
- wenn's nicht paßt, soll ein Stream geöffnet werden
- ein Stream ist meiner Auffassung nach mehr oder weniger ein Filepointer und allenfalls ein geringer Buffer
- leider bringt selbst der Stream einen OutOfMemory-Error, selbst wenn NICHTS im RAM liegt, nur Streams vorhanden sind
- daraus schließe ich, daß die Streams unnötig viel Speicher reservieren, und ich versuche nun, das zu profilen
- danach werd ich wohl sehen, ob es wirklich an den Stream-Klassen liegt, also daß die so riesige Buffer allozieren


----------



## MarcoBehnke (14. Aug 2007)

was für einen Stream benutzt Du denn?
BufferedInputStream?

Nutzt Du den Stream denn auch, um die Daten zu streamen oder liest Du mit den Stream alles in den Hauptspeicher, um dann damit zu arbeiten?


----------



## SlaterB (14. Aug 2007)

@Rock Lobster

ja sorry, 
ich habe halt nicht immer alles im Kopf, mein Arbeitsspeicher ist auch begrenzt,
und den Stream des Threads hier  habe ich nicht nochmal komplett durchlaufen


----------



## Rock Lobster (14. Aug 2007)

@ Marco: Jepp, den BufferedInputStream. Und laut Profiler belegt eine Instanz davon tatsächlich so viel Speicher, wie die Datei groß ist. Zumindest in meinem momentanen Test (da habe ich nur eine 3 MByte-Datei verwendet).

Und ich will eigentlich nur drauf streamen, sprich, er soll nur als FilePointer und -Handle fungieren, und nicht gleich den ganzen Inhalt verfügbar haben. Allerdings brauche ich eine Klasse, bei der "markSupported" gegeben ist. Gibt es Alternativen? Wie gesagt, RandomAccessFile scheidet ja schon aus, das ist nämlich eine der ersten Möglichkeiten, und gestreamt wird ja nur, wenn diese Möglichkeit, die Tempfile-Möglichkeit und die RAM-Möglichkeit fehlschlagen.

@ SlaterB: Jo, ich sehe, wir verstehen uns


----------



## MarcoBehnke (14. Aug 2007)

Hm... das überrascht mich jetzt aber wirklich.

Vielleicht hilft Dir dies hier weiter
https://java.sun.com/j2se/1.4.2/docs/api/javax/sound/sampled/spi/AudioFileReader.html
https://java.sun.com/j2se/1.4.2/docs/api/javax/sound/sampled/AudioInputStream.html


----------



## Rock Lobster (14. Aug 2007)

So langsam habe ich den Verdacht, daß "markSupported" nur deswegen der Fall ist, WEIL er so viel buffert und darin dann beliebig rumspringen kann. Aber jedesmal den Stream neu zu initialisieren ist eben nicht besonders performant.

Schein aber, als müßte ich wohl wirklich darauf ausweichen. Einen normalen Stream nehmen, den ich dann jedesmal neu initialisieren darf, das ist dann zwar saulangsam, aber immerhin bekomme ich keine Speicherprobleme.

@ Marco: Danke für die Tips, leider habe ich damit schon gearbeitet und es bringt nichts. AudioInputStream arbeitet im Grunde genau wie der BufferedInputStream, d.h. ich hatte dieselben Probleme damals. Den AudioFileReader hingegen kann ich nicht nutzen, weil überall, wo dieser funktionieren würde, funktioniert auch das RandomAccessFile... :/


----------

