# Garbage Collector funktioniert nicht richtig?



## pocketom (21. Jun 2007)

Hi,

ich weis das wurde schon oft in der ein oder anderen Form angesprochen. Ich finde aber leider keinen vernünftigen Post der mir dieses Phänomen so erklärt das ich es lösen könnte, das simple Beispiel das ich weiter unten konstruiert habe widerspricht sich mit all dem was ich über den GC gelesen habe.
Deshalb habe ich das kleine Programm geschrieben, mit dem kann man gut demonstrieren wie sich der Hauptspeicher rapide vollmüllt. Ich finde einfach keine Möglichkeit den Speicher aufzuäumen, und das obwohl das Programm nur sehr wenige Zeilen Code hat und doch eigentlich super überschaubar ist!! ! :autsch: 


Das "Programm" hat eigentlich kaum Code, es hat zwei Buttons, mit dem einen erzeugt man "Müll", sprich ein 10M großes Array, mit dem anderen Button ruft man einfach nur den Garbage Collector auf. Der System.gc() tut aber einfach mal garnichts!!! Was ist das für ein Mist???
Referenzen dürfen eigentlich nicht mehr vorhanden sein da der Müll doch eh in einem eigenen Block erzeugt wird, der explizite Aufruf des GC sollte also eh überflüssig sein...?
Ich raffs einfach nicht, es ist zum heulen :bahnhof:


```
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;


public class AppMain{    
	
	
	public static void main(String[] args) throws Exception
	{	
		Display display = new Display();
		final Shell shell = new Shell(display);
		shell.setLayout(grd);
		shell.open();
		
		// Dropdownmenu
		Menu bar = new Menu (shell, SWT.BAR);
		shell.setMenuBar (bar);
		MenuItem fileItem = new MenuItem (bar, SWT.CASCADE);
		fileItem.setText ("&File");
			// Submenu			
			Menu filesubmenu = new Menu (shell, SWT.DROP_DOWN);
			fileItem.setMenu (filesubmenu);
			
			
				
				// Menuitem Use Memory
				MenuItem usemem_item = new MenuItem (filesubmenu, SWT.PUSH);
				usemem_item.setText ("Use Memory");

				usemem_item.addListener (SWT.Selection, new Listener () {
					public void handleEvent (Event e) {
						
						////////////////////////////
						// MAKE A LOT OF GARBAGE //
						//////////////////////////
						int arraysize = 10000000;
						char chararray[] = new char[arraysize];
						
						for(int i=0;i<arraysize ;i++)
							chararray[i]=0;						
					}
				});				
				
				// Menuitem Run GC
				MenuItem gc_item = new MenuItem (filesubmenu, SWT.PUSH);
				gc_item.setText ("Run GC");

				gc_item.addListener (SWT.Selection, new Listener () {
					public void handleEvent (Event e) 
					{						
						/////////////////////////
						// COLLECT GARBAGE !!!//
						///////////////////////
						System.gc();
					}
				});

		while (!shell.isDisposed ()) {
			if (!display.readAndDispatch ()) display.sleep ();
		}		
		display.dispose ();
	}
}
```


----------



## Beni (21. Jun 2007)

"gc()" bittet die VM nur den Garbage Collector laufen zu lassen, aber es gibt keine Garantien, dass er tatsächlich aufgerufen wird.

Der GC sollte auf jedenfall automatisch anspringen, wenn der Speicher knapp wird.


----------



## Wildcard (21. Jun 2007)

Davon abgesehen kann ein Objekt nicht mit einem Durchlauf zerstört werden, dafür sind mindestens 2 nötig.
Erst muss der finalizer aufgerufen werden und danach geprüft werden ob ein Zombie entstanden ist.


----------



## pocketom (21. Jun 2007)

D.h. was müsste ich effektiv in meinem Beispiel tun um Speicher freiwerden zu lassen (aussser die Anwendung beenden, das ist das einzige mir bekannte was funktioniert)?  Den GC zwei mal laufen lassen bringt auch nix. Ich möchte auf keinen Fall warten bis der Speicher komplett vollgelaufen ist und dann hoffen das der GC sich gnädig zeigt. Auf meinem Rechner laufen schliesslich noch andere Sachen, da es ein Server ist können da auch jederzeit Sachen gestartet werden, darauf muss man doch auch in Java Rücksicht nehmen können oder?  :?


----------



## Wildcard (21. Jun 2007)

Du musst erstmal unterscheiden zwischen Speicher den die VM verwendet und Speicher der allokiert wurde.
Einmal allokierter Speicher wird nicht sofort freigegeben, falls er wieder gebraucht wird (denn der Vorgang ist teuer).
Das heißt, dein Java Prozess hat einen gewissen Speicherbereich, verwaltet diesen allerdings selbst und hat in diesem Bereich noch Platz sobald das Array freigegeben ist.
Wenn du ein aggressiveres Verhalten möchtest, lässt sich das teilweise über VM Parameter regulieren.
Solche Mini-Beispiele sollte man aber auch nicht überbewerten, da sie mit einem realen Programm wenig gemein haben   :wink:


----------



## Jango (21. Jun 2007)

Der GC hat als Thread eine sehr niedrige Priorität und springt möglicherweise garnicht an (ausgenommen der Heap ist voll). Gönne deinem Programm mal ne Ruhephase, damit der GC auch seine Arbeit machen kann.


----------



## pocketom (22. Jun 2007)

Das Problem ist, in meinem realen Beispiel öffne ich recht große Dateien(>100 MB, in naher Zukunft bis zu 500 MB), was wir ja in einem anderen Thread bereits ausführlich besprochen haben (www.java-forum.org/de/topic50831_gc-warning-repeated-allocation-of-very-large-block.html). Das Öffnen, Parsen und Speichern geht mittlerweile sehr flott, nicht zuletzt dank der Hilfe von Wildcard und ein paar anderen Usern aus diesem Forum. Darüber bin ich auch sehr zufrieden. Entschliesst sich der User jedoch 3-4 solcher Dateien zu öffnen ohne das Programm zwischendurch zu schliessen, dann hat man ruckzuck 2 GB im RAM, danach gehts eiskalt ans auslagern. Der GC tut absolut nichts. Das kann so einfach nicht richtig sein.

Ich vermute das es mit den Listenern zusammenhängt. Das Listener Konzept ist meiner Meinung nach eine sehr fragwürde Sache. Über den Menüpunkt rufe ich meine Funktion FileOpen() auf, sprich im Listener des Buttons. Dort wird ein Object namens FileParser erzeugt, die Methode FileParser.read(File file) liefert dann die Objektrepräsentation des Files zurück(Megaobjekt, da Collection mit 200 000 - 1 000 000 Objekten), geht aber wiegesagt prima und sauflott. Da ich danach rechenintensive Methoden immer stets über die gesamte Collection laufen lasse und unter anderem sortiere ist das auch notwendig und sinnvoll. Ich habe ein einziges Objekt von diesem Datentyp, das wird zu Beginn (leer) unter main() erzeugt und stets mit dem Rückgabwert von read(file) überschrieben. 

Der Listener sollte meiner Meinung nach trotzdem keine Referenz mehr auf das erzeugte Objekt halten können( Objekt wird schliesslich in einer eigenen Funktion erzeugt, also in einem eigenen Block, das alte wird überschrieben).
Wenn man jedoch erneut ein File öffnet MUSS das alte Object verworfen werden! Ich meine, natürlich habe ich auch schon längst über eine FileClose() Funktion nachgedacht, in C++ würde ich mein Object einfach zerstören und dabei drauf achten das kein Thread mehr drauf zugreift. Kann ich hier aber scheinbar garnicht machen, eine FileCLose() Funktion macht also überhaupt keinen Sinn. Es muss doch eine Lösung geben. Ich kann nicht drauf warten das Gott dem GC irgendwann in den Allerwertesten tritt  :cry:


----------



## byte (22. Jun 2007)

Läuft denn da am Ende der Heap voll? Ist schon komisch. Der GC sollte auf jeden Fall anspringen, bevor der Speicher volläuft.

Hast Du das selbe Beispiel mal mit Swing getestet? Meine Vermutung ist, dass es ein SWT-Problem sein könnte. Bei SWT musst Du ja z.B. selbst erzeugte Widgets auch selbst disposen, bevor der GC sie entfernen kann. Evtl. trifft ja ähnliches auf die Listener zu (was natürlich dämlich wäre).


----------



## tfa (22. Jun 2007)

pocketom hat gesagt.:
			
		

> Das Problem ist, in meinem realen Beispiel öffne ich recht große Dateien(>100 MB, in naher Zukunft bis zu 500 MB), was wir ja in einem anderen Thread bereits ausführlich besprochen haben (www.java-forum.org/de/topic50831_gc-warning-repeated-allocation-of-very-large-block.html). Das Öffnen, Parsen und Speichern geht mittlerweile sehr flott, nicht zuletzt dank der Hilfe von Wildcard und ein paar anderen Usern aus diesem Forum. Darüber bin ich auch sehr zufrieden. Entschliesst sich der User jedoch 3-4 solcher Dateien zu öffnen ohne das Programm zwischendurch zu schliessen, dann hat man ruckzuck 2 GB im RAM, danach gehts eiskalt ans auslagern. Der GC tut absolut nichts. Das kann so einfach nicht richtig sein.



Wie kontrollierst Du denn, ob der GC was tut? Nur mit dem Taskmanager?
Lass Dir mal das anzeigen, was Runtime.freeMemory(), Runtime.maxMemory() und Runtime.totalMemory() zurückliefern.  Der Taskmanager bekommt von den GC-Aktivitäten zunächst nichts mit, da der vom BS allozierte Speicher nicht sofort zurückgegeben wird, wie Wildcard schon schrieb.

Thomas


----------



## pocketom (22. Jun 2007)

Hi,

also, ich habe jetzt mal vor dem Laden in die FileOpen() Funktion folgendes eingebaut:

_
				Runtime.getRuntime().runFinalization();
				System.gc();
				Runtime.getRuntime().gc();
_

mir ist recht bald aufgefallen das ich jetzt mehr Dateien hintereinander laden muss um den entsprechenden Workload zu erzeugen. Das habe ich dann auch nochmal Runtime.freeMemory() und Runtime.totalMemory() überprüft(Runtime.maxMemory() liefert den die Größe des maximalen Adressbereichs, liegt anscheinend irgendwo bei 16 TB):

_
free: 520192
tota: 2125824
free: 2514944
tota: 230895616
free: 38739968
tota: 491991040
free: 43057152
tota: 718483456
free: 134664192
tota: 961753088
free: 116969472
tota: 970141696
free: 115843072
tota: 978530304
_


Siehe da, jetzt ist irgendwann Schluss, und zwar in etwa wenn in meinem RAM 1,4 GB liegen. Das ist schonmal um Klassen besser so. Leider immernoch knapp 1 GB das übeflüssig da rumliegt. Würd ich ja auch noch gern loswerden.

Bin bei meiner Recherche auf "Weak References" und andere Arten von References gestossen. Steige da nur leider überhaupt nicht durch, bzw. ob das hilfreich sein könnte oder ob eher irgendwas gegen den Einsatz spricht kann ich im Moment nicht so wirklich beurteilen. Was meint Ihr dazu?

Edit: Ich werde sobald als möglich auf SWING umsteigen. Davon hab ich zwar null Ahnung, hoffe aber das ich wenigstens teilwese das Wissen das ich durch SWT gewonnen habe gebrauchen kann.


----------



## byte (22. Jun 2007)

Wenn Du SWT kannst, dann ist der Umstieg auf Swing kein Problem.


----------



## Wildcard (22. Jun 2007)

Die WeakReferenzes werden deine Bedenken bezüglich den Listenern zerstören  :wink: 
An sich ist ein explizites abhängen der Listener zu bevorzugen (deshalb muss es *immer* eine removeListener Methode geben), aber es gibt Spezialfälle in denen das nicht möglich ist.
Hier eignen sich WeakReferences sehr gut um WeakListener zu bauen.


----------



## pocketom (22. Jun 2007)

Hmm, da ich die Funktion ja aus meinem Dropdownmenu auswähle (ganz normales Standardmenü: File -> Open, File -> Save,....)kann ich den Listener ja im Prinzip nicht aushängen oder etwa doch?

Bzgl. der weak references habe ich mal einen neuen thread gestartet da das offenbar noch nicht so recht diskutiert wurde hier. So haben es andere User evtl. auch leichter einen Ansatz zur Verwendung dieser weak references zu erhalten:

Speicherresourcen schonen - WeakReferences richtig einsetzen


----------

