# Arbeitsspeicher wieder freigeben



## TomK79 (4. Okt 2012)

Hallo,

ich bin gerade dabei Java zu lernen und beschäftige mich nun schon geraume Zeit damit. Ich kenne mich aber noch nicht ganz so gut damit aus wie z.B. C/C++ oder Assembler.

Ich bin gerade dabei eine kleine GUI-Anwendung (swing) als ersten Gehversuch zu schreiben, mit der ich den Inhalt einer Datei in einer JTextArea anzeigen lassen kann. Dazu habe ich einen Button definiert, mit dem sich über seine ActionListener eine Methode "void DateiOeffnen()" ein JFileChooser öffnen lässt. Mit diesem wird eine Datei geöffnet, zeilenweise in einen String geschrieben und dieser dann in einer JTextArea angezeigt. Das Ganze wurde mit javac compiliert und in eine JAR Datei gepackt.
Dies funktioniert auch alles so, wie es soll.

Mein Problem dabei ist, dass wenn eine neue Datei geöffnet wird der Arbeitsspeicher nicht wieder freigegeben wird.
Das Programm selbst erstellt einen Windows Prozess mit ca. 50MB Arbeitsspeicherverbrauch. Wenn ich jetzt eine Datei mit 350 KB Größe öffne, verbraucht der Prozess ca. 380 MB(!) Arbeitsspeicher. Wenn ich anschließend eine winzige 1KB Datei öffne, sind immer noch 380 MB Arbeitsspeicher belegt. Dieser wird erst wieder freigegeben, wenn ich das Programm schließe.
Die Variablen werden immer wieder verwendet (direkt unter der Klasse definiert). Und ich habe auch alle neu erzeugten Objekte am Ende immer wieder gleich null gesetzt, so dass diese keinen Speicherplatz mehr verbrauchen sollten. Dies scheint nicht zu funktionieren.

Lange Rede, kurze Frage: wie bekomme ich Objekte zuverlässig wieder gelöscht, so dass diese nachdem sie nicht mehr benötigt werden keinen Speicherplatz mehr benötigen?

Hier mal ein Code-Beispiel von meiner Methode:

```
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

class GUI extends JPanel... {
	// Dateivariablen
	File Datei;
	FileReader DateiReader;
	BufferedReader DateiBufferedReader;
	int DateiRueckgabeWert;
	String DateiName, DateiPfad, DateiAbsolut, DateiInhalt, DateiLine;

        ...
        hier steht alles, was die GUI Elemente betrifft.
        ...

	void DateiOeffnen() {
		dateidialog = new JFileChooser();
		DateiRueckgabeWert = dateidialog.showOpenDialog(null);
        // Abfrage, ob auf "Öffnen" geklickt wurde
        if(DateiRueckgabeWert == JFileChooser.APPROVE_OPTION) {
			Datei = dateidialog.getSelectedFile();
			DateiName = Datei.getName();
			DateiPfad = Datei.getParent();
			DateiAbsolut = DateiPfad + "\\" + DateiName;
			// Ausgabe des Dateipfad und -namen der ausgewaehlten Datei
			textfeld_dateiinfo.setText(DateiAbsolut);
			// Dateiinhalt in die zentrale Textarea schreiben
			DateiInhalt = "";
			try {
				DateiReader = new FileReader(Datei);
                DateiBufferedReader = new BufferedReader(DateiReader);
                while ((DateiLine = DateiBufferedReader.readLine()) != null){
					DateiInhalt += DateiLine;
					DateiInhalt += "\n";
				}
				DateiReader = null;
				DateiBufferedReader = null;
			}
			catch(Exception e){
				e.printStackTrace();
			}
			textarea1.setText(DateiInhalt);
			dateidialog = null;
        }
	}
}
```


----------



## jamesv (4. Okt 2012)

hey du benötigst noch einen flush().

Hier mal ein Link zur Insel 

Galileo Computing :: Java ist auch eine Insel – 20.6 Bilder


----------



## Noctarius (4. Okt 2012)

Je nach verwendetem GarbageCollector (die meisten) wird Heap Speicher nur allokiert, aber nicht wieder freigegeben. Die JVM geht davon aus, dass ein Programm, welches einmal den Amount an RAM gebraucht hat, ihn auch wieder benötigt oder alternativ nicht lange genug läuft, dass es stören würde.


----------



## Tomate_Salat (4. Okt 2012)

Du solltest die Datei auch wieder schließen ([c]close()[/c]), nachdem du mit ihr fertig bist. Ansonsten kümmert sich der GC um das Bereinigen deiner Objekte. Du kannst ihn mit [c]System.gc()[/c], bisher hatte ich aber noch nie den Fall, das tun zu müssen.

Ansonsten: In Java sehen die Konventionen etwas anders aus. Variablen/Felder/Methoden werden lowerCamelCase


----------



## Gast2 (4. Okt 2012)

Da hab ich was für dich! 

Tip: Optimierte Freigabe von unbentigtem Heapspeicher durch die SUN Java VM

Ich kenne das "Problem" und habe es durch vorgenannten Link lösen können. Ist sehr ärgerlich, wenn man wegen kurzzeitiger Speicheranforderungen ewig den Speicher belegt.


----------



## dhalsim (4. Okt 2012)

Bevor du dich an irgendwelche VM-Optimierungen machst würde ich aber erst mal den Code in Ordnung bringen. Dazu gehört:

- Scope von Variablen so gering wie möglich halten. Dann sparst du dir auch das unnütze auf null setzen von irgendwelchen globalen Variablen.
- Achte darauf, dass du geöffnete Ressourcen / Reader auch wieder ordentlich schließt und nicht nur aufmachst und dann wegrennst. Der GC räumt zwar nicht mehr genutzte Objekte irgendwann weg, aber keine File Handles usw.
- Namenskonventionen beachten, also z.B. oeffneDatei() statt DateiOeffnen(). Sowas erleichtert das Lesen ungemein.

Du solltest dir auch OOP in Java anschauen. Dein Code riecht noch extrem prozedural, was bei deinen Vorerfahrungen mit Assembler und C auch erst mal normal ist. Nur solltest du irgendwann lernen, wie man sowas in Java macht und alte Denkmuster aus C und Assembler ablegen.

Wenn du das alles verstanden / überarbeitet hast, kannst du dir die NIO-API und das Try-with-resources von Java 7 anschauen. Damit kannst du dir speziell solche Codeteile um einiges einfacher machen.

Und erst dann kommen irgendwelche Optimierungen.


----------



## bone2 (4. Okt 2012)

dhalsim hat mit allem recht

man setzt variablen in java nicht null, wenn sie nicht mehr gebraucht werden, die räumt der GC ganz alleine auf.


----------



## troll (4. Okt 2012)

jamesv hat gesagt.:


> hey du benötigst noch einen flush().
> 
> Hier mal ein Link zur Insel
> 
> Galileo Computing :: Java ist auch eine Insel – 20.6 Bilder



1) wie kommst du auf verlinktes kapitel ? das eine hat mit dem anderen nichts zu tun ...

2) InputStream.flush() ? OUCH ! ... man kann nur OutputStreams flushen ...

absoluter noob post ...


@Bone2
man setz variabln NULL wenn sie vom GC aufgeräumt werden sollen ... allerdings müssen ALLE referenzen NULL sein ... so lange es immer noch irgendwo mindestens eine referenz gibt wird das objekt nicht aufgeräumt ...

@dhalsim
du hast aber schon mal was vom AutoCloseable-Interface gehört oder ? bei klassen die dies implementieren ist ein explizites closen nicht mehr nötig ... das macht die VM intern ...


----------



## Spacerat (4. Okt 2012)

Tomate_Salat hat gesagt.:


> Ansonsten kümmert sich der GC um das Bereinigen deiner Objekte. Du kannst ihn mit [c]System.gc()[/c] (ähhh... evtl. aufrufen?), bisher hatte ich aber noch nie den Fall, das tun zu müssen.


Das ist eine gute Stelle, mal darauf hinzuweisen, dass man dieses "System.gc()" eigentlich auch gar nicht mehr benötigt, weil der GC eh' nur dann loss rennt, wenn er es für richtig hält. Es war afaik mal dazu angedacht, explizit die Finalizer (<Object>."finalize()") unreferenzierter Objekte aufzurufen. Über dieses "finalize()" aber habe ich mir sagen lassen, es sei "broken by design" (oder wie das hiess ).


----------



## dhalsim (4. Okt 2012)

troll hat gesagt.:


> @dhalsim
> du hast aber schon mal was vom AutoCloseable-Interface gehört oder ? bei klassen die dies implementieren ist ein explizites closen nicht mehr nötig ... das macht die VM intern ...


Danke für den Hinweis, aber das ist mir schon bewusst. Bitte lies dazu meinen Beitrag noch mal. Ich hatte nicht ohne Grund das Try-with-resources-Konstrukt erwähnt.


----------



## Tomate_Salat (4. Okt 2012)

Spacerat hat gesagt.:


> [...]"System.gc()"[...]


Shit ja. Da fehlt einiges :-/. Nein, ursprünglich war da hinter "anstoßen" gestanden und noch etwas von wegen: "Der rennt eh wann er will" . 
Ich sollte meine Posts vorm absenden besser doch noch mal durchlesen


----------



## Noctarius (4. Okt 2012)

troll hat gesagt.:


> @Bone2
> man setz variabln NULL wenn sie vom GC aufgeräumt werden sollen ... allerdings müssen ALLE referenzen NULL sein ... so lange es immer noch irgendwo mindestens eine referenz gibt wird das objekt nicht aufgeräumt ...



Auch totaler Blödsinn, dem GC ist es total egal ob ich eine Referenz auf Null gesetzt habe oder nicht, solange der Scope beendet ist. Nur bei Instanzvariablen, deren Scope noch nicht vorbei ist (weil das haltende Objekt noch lebt) muss ich die Variable explizit auf null setzen um die Referenz freizugeben.


```
public class Foo {
  private Bar bar = new Bar();

  public void doSomething() {
    bar = null; // Hier muss die Referenz explizit "genullt" werden

    Bar bar2 = new Bar(); // Hier ist der Scope am Ende der Methode beendet und der GC r#umt auch ohne explizites nullen auf
  }
}
```


----------



## tfa (4. Okt 2012)

> dem GC ist es total egal ob ich eine Referenz auf Null gesetzt habe oder nicht, solange der Scope beendet ist.


Variablen und Scopes sind völlig egal. Der GC räumt Objekte ab, und zwar dann, wenn die Objekte nicht mehr "erreichbar" sind. Du könntest mit deinem bar2-Objekt in dem Beispiel auch folgendes machen:

```
public class Foo {
  public void doSomething() {
    Bar bar2 = new Bar(); 
    MyGlobalBarRepository.registerObject(bar2);
  }
}
```

Am Ende der Method ist das Variablenscope zwar zerstört, aber das Objekt ist über das MyGlobalBarRepository noch referenziert (etwa in einer LinkedList). Also kann es nicht vom GC zerstört werden.


----------



## Spacerat (4. Okt 2012)

Noctarius hat gesagt.:


> Auch totaler Blödsinn, dem GC ist es total egal ob ich eine Referenz auf Null gesetzt habe oder nicht, solange der Scope beendet ist. Nur bei Instanzvariablen, deren Scope noch nicht vorbei ist (weil das haltende Objekt noch lebt) muss ich die Variable explizit auf null setzen um die Referenz freizugeben.


BTW.: Was genau setzt man denn NULL und was wird ggf. vom GC geschreddert?
Wenn der Inhalt einer Objektvariable nicht mehr benötigt wird, weist man ihr den Wert NULL (oder einen anderen initialen Wert gleichen Typs) zu ohne direkten Einfluss darauf, dass das dadurch ersetzte Objekt nicht auch noch in anderen Scopes present ist.
Abgeräumt werden jedoch nur Objekte, welche in keinem einzigen Scope mehr sichtbar sind, maw: Die Objektreferenz nicht mehr erreicht werden kann.
Zusammenfassend also: Wenn man einer Objekvariable einen neuen Wert zuweist, überlässt man den alten Wert einem anderen Schiksal.
[EDIT]@Spacerat: Du musst viel schneller werden! :lol:[/EDIT]


----------



## Noctarius (4. Okt 2012)

tfa hat gesagt.:


> Variablen und Scopes sind völlig egal. Der GC räumt Objekte ab, und zwar dann, wenn die Objekte nicht mehr "erreichbar" sind. Du könntest mit deinem bar2-Objekt in dem Beispiel auch folgendes machen:
> 
> ```
> public class Foo {
> ...



Richtig, das ist aber anders als mein Beispiel und stimmt an der Stelle ist das Objekt noch erreichbar obwohl der Methodenscope vorbei ist. Hier bringt dir aber auch das "nullen" nichts ;-)

Generell habt ihr recht, ich hätte es noch genauer klar machen müssen, ich wollte nur erstmal das "man muss sie null setzen" widerlegen


----------



## troll (4. Okt 2012)

mir ist bewusst das man nicht "nullen" muss wenn der block beendet wird UND keine anderen globalen referenzen mehr vorhanden sind ...
es ging jedoch generell darum das eben wenn es noch andere referenzen außerhalb des blocks gibt diese NULL (bzw anders) zu setzen sind damit der GC auch merkt dass das objekt weggeräumt werden kann ...
ich finde das beispiel schon richtig mit dem auf andere referenzen außerhalb des scope-blocks hingewiesen wird ... denn sowas wird gerne mal vergessen ... gerade wenn man mit containern arbeitet die "durchgereicht" werden ... denn in diesem fall ist scope wirklich völlig egal ...


@TO
btw ... was mir noch eingefallen ist : da du mit Strings arbeitest musst du auch den String-pool beachten der ebenfalls noch cached ... und diesen pool aufzuräumen geht nur mit reflections (zumindest ist mir kein "normaler" weg bekannt den pool zu leeren) ...

und System.gc() ist eigentlich eher witzlos ... denn der GC entscheided immer noch selbst ob und wann er los läuft ... und was er dabei überhaupt aufräumt


----------



## Firephoenix (4. Okt 2012)

troll hat gesagt.:


> @TO
> btw ... was mir noch eingefallen ist : da du mit Strings arbeitest musst du auch den String-pool beachten der ebenfalls noch cached ... und diesen pool aufzuräumen geht nur mit reflections (zumindest ist mir kein "normaler" weg bekannt den pool zu leeren) ...



Wenn er den memory nicht selber löten muss oder gerade dabei ist die Bibliothek von Alexandira Wort für Wort zu puffern wäre das aber echt eine der letzten Ecken an denen ich optimieren würde ^^

Da sind die Standard-Methoden wie das überprüfen von mitgeschleiften Objekten in andern Containern etc vielversprechender.

Eventuell ist ja auch ein Profiling mittels VisualVM und Konsorten hilfreich (meistens reicht es ja schon den Objekttyp zu sehen der den ganzen Speicher belegt um abzuschätzen wo der Fehler liegt).

Gruß


----------



## Spacerat (5. Okt 2012)

Machen wir's kurz und sagen es mal so:
@TO: Schreib' deinen Code und kümmere dich nicht gross um den Speicher. Sei sparsam mit Heap und verwende da wo geht "java.nio" und DirectBuffer. Einmal alloziierter Heap wird von der JVM nicht mehr freigegeben. Evtl. kann man Mutables ja erneut verwenden (ObjectReuse). Aber bevor man solche Techniken verwendet, sollte das Programm zumindest von der Logik her schon mal gelaufen sein.
Die Sache mit den Scopes geht in etwa so (natürlich auf nativer Basis):

```
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.TreeMap;

class Scope {
	private static final Map<Object, Collection<Scope>> REFERENCES = new IdentityHashMap<>();

	private final Map<String, Object> fields;
	private final Scope parent;

	public Scope(Scope parent) {
		this(parent, new TreeMap<String, Object>());
	}

	protected Scope(Scope parent, Map<String, Object> fields) {
		if(fields == null) {
			throw new NullPointerException();
		}
		this.parent = parent;
		this.fields = fields;
	}

	public void declare(String name) {
		setValue(name, null);
	}

	public void setValue(String name, Object value) {
		checkName(name);
		Object oldValue = null;
		Collection<Scope> sc = REFERENCES.get(value);
		if(sc == null) {
			sc = new HashSet<>();
		}
		if(!sc.contains(this)) {
			try {
				oldValue = fields.put(name, value);
				sc.add(this);
				REFERENCES.put(value, sc);
			} catch(Exception e) {
				if(parent != null) {
					parent.setValue(name, value);
				}
				throw new IllegalArgumentException("no such field " + name);
			}
		} 
		if(oldValue != null) {
			sc = REFERENCES.get(oldValue);
			sc.remove(this);
			if(sc.isEmpty()) {
				REFERENCES.remove(oldValue);
			}
		}
	}

	private void checkName(String name) {
		if(name == null || name.length() == 0 || Character.isDigit(name.charAt(0))) {
			throw new IllegalArgumentException("invalid name");
		}
	}
}

class ClassScope extends Scope {
	public ClassScope() {
		super(null, createFields());
	}

	private static Map<String, Object> createFields() {
		Map<String, Object> fields = new TreeMap<>();
		//... adding some fields
		return Collections.unmodifiableMap(fields);
	}
}
```
...und bevor jemand nach den Sichtbarkeiten und anderen Attributen fragt, *private* und Konsorten zeigen dem Compiler nur, in welchem Scope die Variable deklariert werden soll, das Scope selber kennt die eigene Sichtbarkeit jedoch nicht. Andere Attribute (*static*, *final*, *volatile* usw.) sind 'ne andere Geschichte, die ich hier zu Gunsten der Übersicht ignoriert habe.
[EDIT]Der Field-Getter fehlt  zwar auch, aber der wurde nur schlicht vergessen. [/EDIT]


----------



## Gast2 (5. Okt 2012)

Eure Seitenlange Diskussion ändert nichts am ursprünglichen Problem, dass auch vom GC freigegebener Speicher nicht ans OS zurückgegeben wird. Das war meines Erachtens nach die eigentliche Frage! Der GC gibt Speicher für den aktuellen Java Prozess wieder frei. Hat jemand den Artikel den ich gepostet hab überhaupt gelesen?


----------



## Noctarius (5. Okt 2012)

Noctarius hat gesagt.:


> Je nach verwendetem GarbageCollector (die meisten) wird Heap Speicher nur allokiert, aber nicht wieder freigegeben. Die JVM geht davon aus, dass ein Programm, welches einmal den Amount an RAM gebraucht hat, ihn auch wieder benötigt oder alternativ nicht lange genug läuft, dass es stören würde.



Das sagte ich doch davor schon ;-)


----------



## Spacerat (5. Okt 2012)

kappesf hat gesagt.:


> Eure Seitenlange Diskussion ändert nichts am ursprünglichen Problem, dass auch vom GC freigegebener Speicher nicht ans OS zurückgegeben wird. Das war meines Erachtens nach die eigentliche Frage! Der GC gibt Speicher für den aktuellen Java Prozess wieder frei. Hat jemand den Artikel den ich gepostet hab überhaupt gelesen?


Aber sicher doch und ja, du hast Recht. Am wenigsten aber kann man Speicher durch NULL setzen freigeben, das ist der Faden der entbrannten Diskussion. 
DirectBuffers belegen dabei nur sehr wenig Heapspeicher und sonst nur "extern" alloziiertes Ram, welches auch wieder freigegeben werden kann. Das wiederum ändert aber auch nichts an der Tatsache, dass man auch bei DirectBuffern an die vorgegebenen Speicherlimits gebunden ist.
ByteBuffer (Java 2 Platform SE v1.4.2)


----------



## Gast2 (5. Okt 2012)

Noctarius hat gesagt.:


> Das sagte ich doch davor schon ;-)



Dich mal ausgenommen... Wollte den Artikel ja eben ergänzend zu deinem Post anfügen, da der Sachverhalt und auch die Einstellmöglichkeiten gut dargestellt werden.


----------

