# Java OutOfMemory Error verhindern, Resourcenbedarf präventiv abschätzen?



## Bergtroll (24. Jul 2010)

Hallo mal wieder, werte Java Gemeinde,

habe eine neue Kopfnuss, die zu knacken ihr mir vielleicht helfen könnt. Abhängig von der Größe eines vom User zu wählenden Datensatzes (im Bereich kb - mehrere GB) wird zwischen einer In-Memory Strategie und einer Festplatten Caching Strategie gewählt. Damit zerfallen die möglichen Aufgaben erstmal in zwei Klassen:

1) Diejenigen, die auf jeden Fall mit den Resourcen klarkommen.
2) Diejenigen, die auf Kosten der Performance mit Festplatten Cache arbeiten.

Kopfzerbrechen bereiten mir die Datensätze im Grenzbereich dieser Kategorien. Auf einem schnellen Multicore Prozessor räumt die GC so schnell auf, dass stetig genug Speicherplatz da ist, während auf einem langsameren bereits der Memory Error auftritt.

- Wie schätzt ihr den zu erwartenden Resourcenverbrauch im Vorfeld sicher ab, um eine der beiden Strategien zu wählen?
- Wie könnte man eine Fallbacklösung gestalten, die sofern sich mit Lösung 1) ein Memory Error so langsam ankündigt abbricht, GC ausführt, den User informiert und Lösung 2) verwendet? Gibt es Erfahrungen mit einem sinnvollen Schwellwert für die Speicherbelegung, ab dem dies geschehen sollte?
- Wie rettet man die JVM, falls es mal mit dem Abbruch nicht mehr klappt?
- Welche Werkzeuge kennt ihr, die einen bei dieser Aufgabe unterstützen?

EDIT:
- Wie könnte man die Aufgabe kurz pausieren, die GC anweisen, zu tun was gerade geht und evtl. erstmal darauf zu warten, andere Threads abzuarbeiten und es dann erst nochmal zu versuchen.

EDIT2:
Nur um diesbezüglichen Unklarheiten vorzubeugen. Es geht nicht um MemoryLeaks sondern um Prozesse, die temporär hohe Spitzen im Speicherverbrauch haben und danach wieder aufgeräumt werden dürfen.

Mfg,
Bergtroll


----------



## Marco13 (24. Jul 2010)

Eigentlich sollte die Frage, ob es zu einem OutOfMemoryError kommt, nicht von der Geschweindigkeit des Rechner abhängen. Also _eigentlich_ sollte, wenn der Error kommt, unabhängig von der Geschwindigkeit, ALLES getan worden sein, was getan werden kann, damit genug Speicher frei ist. 

Sowas wie [JavaSpecialists 092] - OutOfMemoryError Warning System hast du vermutlich schon gefunden. Ansonsten könntest du mal in der Gegend und "Memory Mapped Files" schauen, ob die dir helfen könnten. (Erst dachte ich noch an SoftReferences, aber du willst die Daten ja nicht verlieren, wenn der Speicher knapp wird... Man könnte höchstens überlegen, ob man die Daten der SoftReferences noch irgendwie auf die Platte schreiben kann, wenn sie kurz davor sind, in den Müll geworfen zu werden, aber das klingt erstmal gewagt...)


----------



## Bergtroll (24. Jul 2010)

Hi Marco, hab ja fast drauf gewettet, dass du als erster (und wahrscheinlich als einziger?) antwortest :-D. 

Leider ist das mit dem "eigentlich" nicht so weit her, denn genau dieses Problem tritt bei mir auf, wenn ich mit gleichen Memory Einstellung zu Hause und in der Uni das Programm laufen lasse... Ich vermute, das Problem kann dann auftreten, wenn man viele kleine Zwischenschritt Objekte kurzer Lebensdauer erzeugt, die nicht schnell genug weggeräumt werden. Ich frage mich natürlich, ob es möglicherweise immer einen Weg gibt, wie man solche Zwischenschritt Objekte vermeiden könnte...?

Na wie dem auch sei, auf Systemen wo Strategie 1 gerade so klappen könnte, könnte man dann in meiner Vorstellung je nach Einstellung nen try & (almost) error Versuch wagen, ob Strategie 1 funktioniert. Die Memory Mapping Dinger schaue ich mir mal an.

EDIT
Und danke für den Artikel


----------



## Bergtroll (24. Jul 2010)

Also ich habe gerade nochmals nachgeschaut, der Speicherverbrauch für die selbe Aktion ist hier auf meinem Rechner etwa 300MB höher als Remote auf dem Multicore Unirechner. Kann mir das nicht richtig erklären...


----------



## Marco13 (25. Jul 2010)

Das erklärt natürlich die Frage, wieso sich die Programme unterschiedlich verhalten, wirft aber die Frage auf ... ... .. warum sie sich unterschiedlich verhalten ... Äh. Ja. Gleiches Betriebssystem, gleiche Architektur (32/64 bit), gleiche JVM...? 

Insgesamt ist sowas natürlich immer schwierig. Das genaue Verhalten der JVM in bezug auf GC ist glaube ich nicht "spezifiziert". Es gibt unterschiedliche GCs, und SEHR (!) viele magische JVM-Startparameter, die für das Finetuning des Garbage Collectors verwendet werden können (und ... ich glaube in einer der letzten Versionen gab's doch sogar einen komplett neuen GC..? Kann mich aber täuschen, vielleicht ist der auch erst für Java 7 geplant). 

Sorry, wirklich weiterhelfen tut das jetzt wohl nicht. Aber ich denke, das was im verlinkten Artikel beschrieben ist, könnte ein Ansatzpunkt sein. 

Aber angnommen, es gibt diesen magischen Listener

```
MemoryWarningSystem mws = new MemoryWarningSystem();
mws.addListener(new MemoryWarningSystem.Listener() {
  public void memoryUsageLow(long usedMemory, long maxMemory) 
  {
     // .... 
  }
}
```
Dann wäre es schon spannend, was bei den "..." stehen sollte. Also, man müßte ja die Daten in einer Form vorhalten, dass man Teile davon "mal kurz" auf die Platte spülen kann. Die Frage, welche man dafür wählt wäre spannend, schon ein einfaches "Least Recently Used" muss ja verwaltet werden. Und bei Bedarf sollte man die Daten (automatisch und ... im Idealfall, auch vollkommen transparent für den Benutzer) nachladen können. Ganz konsequent weitergedacht ginge das ja (etwas übertrieben formuliert  aber von der Idee her) grob in Richtung einer "VM mit dynamischem -Xmx" .... an sich wär' das ja schon was ... :reflect:


----------



## Gast2 (25. Jul 2010)

Moin,

wie wäre es gleich von Anfang an eine Auslagerung auf die Platte zu Planen - die Daten werden aber intern im Speicher vorgehalten ... und nur wenn der Speicher kanpp wird, wird wirklich auf die PLatte ausgelagert ... Paging wie beim Betriebssystem quasi

hand, mogel


----------



## Bergtroll (25. Jul 2010)

Hallo Ihr Beiden und danke schonmal für die Rückmeldungen. In Anbetracht dessen, dass ich mit meiner Arbeit jetzt erstmal fertig werden muss, werde ich das Problem nun wohl doch erstmal zurückstellen und zu gegebener Zeit wieder aufgreifen. Es ist augenscheinlich nämlich doch schwieriger zu lösen, als ich gehofft habe.

Das Betriebssystem ist übrigens in beiden Fällen WinXP-64, allerdings benutze ich auf dem Unirechner eine 64 bit Java VM, während ich hier eine 32 bit JVM benutze. Später teste ich wenn Zeit ist mal aus, was passiert, wenn ich auch hier ne 64 bit JVM benutze. Aber 300MB Unterschied nur wegen der Bit? ...

Wenn das ganze dann soweit steht, schaue ich mir auch an, wo der Verbrauch herkommt. ICh habe nämlich jetzt gelesen, das der Java3D Scenegraph auch ordentlich Speicher frisst. Iss ja eigentlich blöd, dass die Datenlast des Szenegraph, also Texturinfo, Polygonecken, etc. alle im Main Memory UND im GPU Memory rumhängen, selbst wenn sie sich selten ändern. 

Schade, dass es nicht mehr weiterentwickelt wird, sonst könnte man mal anfragen, nur Knoten hoher Dynamik, die sich also oft ändern, direkt im Speicher zu halten und den Rest als MemoryMapped File zu haben. Keine Ahnung ob das funktionieren würde???

So, ich muss jetzt erstmal bissi Zeug in die Eclipse Worker API auslagern und meine Doku und Hilfeseiten fertig schreiben und sollte ich das bis heute Abend schaffen ;-), mache ich auf dieser Baustelle hier weiter. 

BTW, für uns als Entwickler gibts ja Profiler, so das wir schauen können, was wo wie belastet wird. Ich würde dem Enduser evtl auch gerne die Möglichkeit geben, sich die Speicherverwendung von JSciVision anzuschauen. Also in der Status Leiste vielleicht nur "x MB von X MB benutzt" und dann bei Klick die genaue Verteilung. Das würde mir dann überdies später die Möglichkeit bieten, mir von Bugeinreichungen der Benutzer entsprechende Protokolle mitliefern zu lassen... Wurde doch bestimmt schon irgendwie umgesetzt?


----------



## Bergtroll (25. Jul 2010)

Ich kannte die Java Specialist Newsletter tatsächlich noch nicht und bin umsomehr froh darüber, dass dieser Zustand jetzt vorüber ist. Ich habe mir nun doch den ganzen Tag erstmal dieses MemoryWarningSystem angeschaut und über das GC Konzept der HotSpot VM gelesen. Habe dieses Warning System ausm Newsletter mal in meinen Workspace gepastet und ne abgewandelte Testklasse benutzt um ein wenig zu spielen.

Es lässt sich feststellen, dass sich der Zeitpunkt, wann über die Überschreitung des Thresholds informiert wird, nicht wirklich determinieren lässt und auch nichts mit der eingestellten Maximalgröße des Heap zu tun zu haben scheint. Bei sehr kleinem Xmx (5MB) und einem LOW_MEMORY_THRESHOLD von 0.5 wird erst bei etwa 0.87... über die Überschreitung informiert. Generell scheint es so zu sein, dass je höher der Threshold ist, desto zeitlich näher die Information darüber durch den Notifier erfolgt. 

Während also ein CRITICAL_THRESHOLD von 0.80 z.B. teilweise erst bei einer Speicherbelegung von 0.97 mitgeteilt wird, führt mit allen von mir manuell getesteten Xmx Einstellungen ein CRITICAL_THRESHOLD selbst von 0.9999999999 noch zum korrekten Beenden des Tests ohne OutOfMemory Error. Das deckt sich mit der Dokumentation in MemoryMXBean, wo gesagt wird, dass es keine Garantie dafür gibt, wann genau die Mitteilung erfolgt, aber das die Prüfung zur GC Zeit erfolgt. 

Somit wäre auch erklärt, warum selbst der maximale kritische Grenzwert noch zum ordentlichen Terminieren führt, da ja der OutOfMemory Error per Definition erst geworfen wird, wenn die GC nix mehr freiräumen kann und die Notification zum GC Zeitpunkt erfolgt. Daraus folgt für mich aber auch, dass eine sinnvolle Anwendung dieser Methode auch erst bei einer relativ hohen Speicherbelegung erfolgen kann, wo die GC häufiger ausgeführt wird.

Wäre nun nur noch die Frage, wie ich einen Thread zu einem beliebigen Zeitpunkt zum Aufhören und Aufräumen bewegen kann...


```
import static org.junit.Assert.*;

import java.util.Collection;
import java.util.LinkedList;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class MemoryWarningSystemTest {

	private static final double LOW_MEMORY_THRESHOLD = 0.8;
	private static final double CRITICAL_MEMORY_THRESHOLD = 0.90;
	private static final double THRESHOLD_DELTA = 0.09;

	private MemoryWarningSystem memWarningSystem;
	private MemoryWarningSystem.Listener lowMemListener;
	private Collection<Double> numbers;

	volatile boolean abort = false;
	volatile double percentageUsed = 0.0;
	volatile boolean memExtended = false;

	@Before
	public void setup() {
		MemoryWarningSystem.setPercentageUsageThreshold(LOW_MEMORY_THRESHOLD);
		memWarningSystem = new MemoryWarningSystem();
		lowMemListener = new MemoryWarningSystem.Listener() {
			@Override
			public void memoryUsageLow(long usedMemory, long maxMemory) {
				lowMemNoteOccured(usedMemory, maxMemory);
			}
		};
		memWarningSystem.addListener(lowMemListener);
		numbers = new LinkedList<Double>();
	}

	@After
	public void tearDown() {
		memWarningSystem = null;
		lowMemListener = null;
		numbers = null;
	}

	@Test
	public void shutdownAtMemoryWarningTest() {
		while (true) {
			if (abort) {
				if (memExtended && percentageUsed > CRITICAL_MEMORY_THRESHOLD) {
					assertEquals(CRITICAL_MEMORY_THRESHOLD, percentageUsed,
							THRESHOLD_DELTA);
					break;

				} else if (!memExtended
						&& percentageUsed > LOW_MEMORY_THRESHOLD) {
					assertEquals(LOW_MEMORY_THRESHOLD, percentageUsed,
							THRESHOLD_DELTA);
					MemoryWarningSystem
							.setPercentageUsageThreshold(CRITICAL_MEMORY_THRESHOLD);
					memExtended = true;
					abort = false;

				} else {
					fail("Memory warning did not interrupted");
				}
			}

			numbers.add(Math.random());
		}

	}

	private void lowMemNoteOccured(long usedMemory, long maxMemory) {
		abort = true;
		percentageUsed = ((double) usedMemory) / maxMemory;
		System.out.println("Memory usage low!!!");
		System.out.println(String.format("usedMem: %d / MaxMem: %d",
				usedMemory, maxMemory));
		System.out.println("percentageUsed = " + percentageUsed);
	}

}
```


----------



## Bergtroll (25. Jul 2010)

So das Problem ist, ich benutze eine Bibliothek. In dieser gibt es ein Display Objekt mit einer Methode, die ein umfangreiches Processing anstößt und das Potential zum OutOfMemory nach sich zieht. Von aussen, also von meiner Position als Bibliotheksbenutzer aus betrachtet, ist die Methode nur ein einzelner Aufruf. 

Wenn ich jetzt diesen einzelnen Aufruf in einen eigenen Thread einpacke, weiß ich leider nicht, wie ich diesem mitteilen kann, dass er aufhören soll, die Methode (und alle abhängigen Unterprozeduren) weiter abzuarbeiten und stattdessen aufzuräumen und sich zu beenden. :bahnhof:


----------



## Marco13 (25. Jul 2010)

Hm ... vielleicht hab' ich da jetzt was falsch verstanden, aber mehr als ein thread.interrupt() kann man da glaub' ich erstmal nicht machen...!? :bahnhof:


----------



## Bergtroll (8. Aug 2010)

Ich überlege, ob ich den Arbeitsthread als Deamon Thread erzeuge. Wenn Memory knapp wird, kille ich einfach dessen Mutterthread, wodurch der Deamon sofortigst angehalten und gleich mitgenommen wird, stimmt die Theorie?


----------



## EditorEdi (8. Aug 2010)

Bergtroll hat gesagt.:


> Ich überlege, ob ich den Arbeitsthread als Deamon Thread erzeuge. Wenn Memory knapp wird, kille ich einfach dessen Mutterthread, wodurch der Deamon sofortigst angehalten und gleich mitgenommen wird, stimmt die Theorie?



Deamons werden dann angehalten, wenn es keine anderen aktiven Threads außer Deamons gibt.


----------



## Bergtroll (8. Aug 2010)

Hmm... dann hat sich das auch erledigt... Dann muss es wohl dich die deprecated stop methode werden... :-(


----------



## EditorEdi (8. Aug 2010)

Daemon heißen die speziellen Threads übrigens.



Bergtroll hat gesagt.:


> Dann muss es wohl dich die deprecated stop methode werden... :-(



Habe das Thema nicht weiter verfilgt.


----------



## Bergtroll (8. Aug 2010)

Werden alle ausschließlich im neuen Thread existenten und erzeugten Objekte nach Abwürgen desselbigen von der GC weggeräumt? Das Galileo OpenBook sagt nämlich



> deprecated gibt uns schon einen guten Hinweis darauf, stop() besser nicht zu benutzen. (Leider gibt es hier, im Gegensatz zu den meisten anderen veralteten Methoden, keinen einfachen, empfohlenen Ersatz.) Überschreiben können wir stop() auch nicht, da es final ist. Wenn wir einen Thread von außen beenden, geben wir ihm keine Chance mehr, seinen Zustand konsistent zu verlassen. Zudem kann die Unterbrechung an beliebiger Stelle erfolgen, sodass angeforderte Ressourcen frei in der Luft hängen können.



und ich weiß nicht genau, was das von mir will. Aber ich will definitiv keine resourcen, die ich nicht mehr beseitigen kann, weil sie in der Luft hängen...


----------



## Bergtroll (8. Aug 2010)

> Daemon heißen die speziellen Threads übrigens.



Ah sorry, Verdreher


----------



## EditorEdi (8. Aug 2010)

Denkste, die wären wären zum spaß deprecated 

PS: Sorry, das Trollige habe ich mit von deinem Namen abgekupfert ^^


----------



## Bergtroll (8. Aug 2010)

Nee genau das ist ja mein Problem. Ich setze einen einzigen Befehl zu meiner externen Library ab (Display.setReference(String filePath)), was zu einem längeren processing führt, von wenigen sekunden bis zu mehreren Minuten, je nach aktueller Optionkombination, File, etc. Die Methode wird genau deshalb ja in einem eigenen Thread aufgerufen und meldet sich zurück, wenn Sie ferddisch ist. 

Die Methode guckt aber leider trotz ihrer langen Laufzeit nicht ab und zu nach der Interrupt Variablen, so dass ich, sobald die Methode einmal aufgerufen ist, nur warten kann, ob der Thread richtig terminiert oder vorher wegen memory error abschmiert. Eingreifen kann ich auf dem empfohlenen Wege nicht, weil der Interrupt schlicht ignoriert wird. Und das ist sch.... weil ich ja dank meinem Notifier sehe, wann Memory knapp wird, und theoretisch zum richtigen Zeitpunkt eingreifen könnte. 

Was also tun? Wenn ich die Methode umbaue, breche ich die Kompatibilität zur Originalbib, wenn ich stop benutze, isses deprecated, eine Library Alternative gibt es nicht, wenn ich stattdessen kein C++ nehme oder SEHR viel Geld bezahle.


----------



## Bergtroll (8. Aug 2010)

Okay, ich setze jetzt definitiv Thread.stop() ein, weil das nach Java ist auch eine Insel, Kapitel 11.3.6 Abs. 1 wohl die einzige Möglichkeit ist, einen Thread der den Interrupt Request ignoriert aber dringend beendet werden muss, zu beenden. Falls irgendwem eine schönere Möglichkeit einfällt, die Sache zu lösen, immer her damit.

Mfg
Bergtroll


----------



## H3llGhost (12. Aug 2010)

Also wenn du den Aufbau wie in Post 8 nimmst, würde auch folgendes gehen.
Wird so auch von Oracle vorgeschlagen. 

[java=1]
    private volatile Thread blinker;

    public void stop() {
        blinker = null;
    }

    public void run() {
        Thread thisThread = Thread.currentThread();
        while (blinker == thisThread) {
            try {
                thisThread.sleep(interval);
            } catch (InterruptedException e){
            }
            repaint();
        }
    }
[/code]


----------

