Speicherprobleme in den Griff kriegen

kangaroody

Grünschnabel
Mir ist es vor kurzem passiert, dass ich bei einem DB-Client mit vielen großen Tabellen irgendwann ne OutOfMemoryException bekommen habe. Da ich den Fehler auf Anhieb nicht finden konnte, hab ich mir nen kleines Tool gebaut, mit dem ich beliebige Objekte darauf testen kann, ob sie auch schön brav vom garbagecollector entfernt werden.

Dachte mir, vielleicht kann es von euch ja jemand brauchen.

Code:
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Enumeration;
import java.util.Hashtable;

/**
 * Testklasse mit der geprüft werden kann, ob Objekte schon vom GarbageCollector vernichtet wurden 
 * 
 *
 */
public class ReferenceTester {

	private Hashtable refs = new Hashtable();
	
	private static ReferenceTester single = null;
	
	private ReferenceTester(){
		
	}
	
        /**
        * Singleton
        *
        */
	public static ReferenceTester getTester(){
		if(single==null){
			single = new ReferenceTester();
		}
		return single;
	}
	
	/**
	 * registriert ein zu testendes Objekt
	 * 
	 * @param id - eindeutige ID des Objekts
	 * @param o - zu testendes Objekt
	 */
	public void registerReference(String id, Object o){
		Reference ref = new WeakReference(o);
		refs.put(id, ref);
		System.out.println("Referenz "+id+" registriert");
	}
	
	
	/**
	 * prüft ob das Objekt noch Speicher verbraucht
	 * 
	 * @param id - Objekt-ID
	 * @return - true, wenn Objekt vom GC gekickt wurde, false falls noch nicht, bzw. nicht registriert
	 */
	public boolean testReference(String id){
		Reference ref = (Reference) refs.get(id);
		if(ref!=null){
			if(ref.get()==null){
				System.out.println("Referenz "+id+" wurde vom GarbageCollector vernichtet.");
				return true;
			} else {
				System.out.println("Referenz "+id+" ist noch da");
				return false;
			}
		} else {
			System.out.println("Referenz "+id+" nicht gefunden! Falsche ID?");
			return false;
		}
	}
	
	/**
	 * Testet alle hinzugefügten Referenzen
	 *
	 */
	public void testAllReferences(){
		Enumeration keys = this.refs.keys();
		while(keys.hasMoreElements()){
			String id = (String) keys.nextElement();
			testReference(id);
		}
	}
}

Aufruf sieht dann ungefähr so aus:

Code:
//Dies sind sehr komplizierte Objekte mit langer Lebensdauer, 
//die von vielen verschiedenen Klassen manipuliert werden
Object obby1 = new Object(); 
Object obby2 = new Object(); 
ReferenceTester tester = ReferenceTester.getTester();
tester.add("obby1_id", object);
tester.add("obby2_id", object);


...
...
...


//an einer beliebigen anderen Stelle im Programm
ReferenceTester tester = ReferenceTester.getTester();
tester.testReference("objektid");

//oder 
tester.testAllReferences();




Meinungen/Verbesserungen/Erweiterungen sind natürlich erwünscht.
z.B. könnte man noch ne Funktion hinzufügen, mit der die registrierten Referenzen regelmäßig überprüft werden können.
 
Hallo!

Also ich denke das was du da machst wird so nicht richtig funktinieren... du bekommst nur bei der Überprüfung mit, ob die Instanz hinter der WeakReference gc'ed wurde oder nicht. Wenn du darüber informiert werden möchtest wann ein Objekt vom GC aufgesammelt wurde musst du dazu eine ReferenceQueue verwenden. Die Reihenfolge in der die Reference Repäsentationen der Obejkte aus der ReferenceQueue ausgelesen werdne können ist jedoch nicht deterministisch..

Java:
/**
 * 
 */
package de.tutorials;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @author Tom
 * 
 */
public class GCMonitorExample {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		ExecutorService executorService = Executors.newFixedThreadPool(2);
		final ReferenceQueue referenceQueue = new ReferenceQueue();
		Runnable referenceMonitor = new Runnable() {
			public void run() {
				System.out.println("Reference monitor started!");
				while (true) {
					try {
						Reference reference = referenceQueue.remove();
						System.out.println("Object that was wrapped by "
								+ reference + " was gc'ed");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		};

		final List<PhantomReference> phantoms = new ArrayList<PhantomReference>();
		Runnable objectCreator = new Runnable() {
			public void run() {
				System.out.println("Object creator started!");
				long lastGCTime = System.currentTimeMillis();
				while (true) {
					Object bigObject = new Object(){
						byte[] payLoad = new byte[2 << 16];
					};
					PhantomReference phantomReference = new PhantomReference(
							bigObject, referenceQueue);
					
					phantoms.add(phantomReference);
					
					System.out.println("Wrapped " + bigObject
							+ " with reference " + phantomReference);
					
					try {

						TimeUnit.MILLISECONDS.sleep(200);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					bigObject = null; // clear reference to bigObject
				
					if (System.currentTimeMillis() - lastGCTime > 1000L) {
						System.gc();
						System.gc();
						System.gc();
						System.gc();
						lastGCTime = System.currentTimeMillis();
					}
				}
			}
		};

		executorService.execute(referenceMonitor);
		executorService.execute(objectCreator);
	}
}


Ein Beispiellauf:
Code:
Reference monitor started!
Object creator started!
Wrapped de.tutorials.GCMonitorExample$2$1@17725c4 with reference java.lang.ref.PhantomReference@1506dc4
Wrapped de.tutorials.GCMonitorExample$2$1@a761fe with reference java.lang.ref.PhantomReference@1126b07
Object that was wrapped by java.lang.ref.PhantomReference@1506dc4 was gc'ed
Wrapped de.tutorials.GCMonitorExample$2$1@30d082 with reference java.lang.ref.PhantomReference@c931fc
Wrapped de.tutorials.GCMonitorExample$2$1@f0c0d3 with reference java.lang.ref.PhantomReference@3820e
Wrapped de.tutorials.GCMonitorExample$2$1@4f80d6 with reference java.lang.ref.PhantomReference@193722c
Object that was wrapped by java.lang.ref.PhantomReference@1126b07 was gc'ed
Object that was wrapped by java.lang.ref.PhantomReference@c931fc was gc'ed
Object that was wrapped by java.lang.ref.PhantomReference@3820e was gc'ed
Wrapped de.tutorials.GCMonitorExample$2$1@12cc95d with reference java.lang.ref.PhantomReference@157fb52
Object that was wrapped by java.lang.ref.PhantomReference@193722c was gc'ed
Wrapped de.tutorials.GCMonitorExample$2$1@134e4fb with reference java.lang.ref.PhantomReference@1a52fdf
Wrapped de.tutorials.GCMonitorExample$2$1@1ea0252 with reference java.lang.ref.PhantomReference@3e89c3
Object that was wrapped by java.lang.ref.PhantomReference@157fb52 was gc'ed
Wrapped de.tutorials.GCMonitorExample$2$1@1c695a6 with reference java.lang.ref.PhantomReference@8acf6e
Object that was wrapped by java.lang.ref.PhantomReference@1a52fdf was gc'ed
Object that was wrapped by java.lang.ref.PhantomReference@3e89c3 was gc'ed
Wrapped de.tutorials.GCMonitorExample$2$1@1386918 with reference java.lang.ref.PhantomReference@7bb290
Wrapped de.tutorials.GCMonitorExample$2$1@10849bc with reference java.lang.ref.PhantomReference@120d62b
Wrapped de.tutorials.GCMonitorExample$2$1@ccc588 with reference java.lang.ref.PhantomReference@1bc82e7
Object that was wrapped by java.lang.ref.PhantomReference@120d62b was gc'ed
Object that was wrapped by java.lang.ref.PhantomReference@8acf6e was gc'ed
Object that was wrapped by java.lang.ref.PhantomReference@7bb290 was gc'ed
Wrapped de.tutorials.GCMonitorExample$2$1@1362012 with reference java.lang.ref.PhantomReference@73a34b
Wrapped de.tutorials.GCMonitorExample$2$1@1e59128 with reference java.lang.ref.PhantomReference@9ced8e
Wrapped de.tutorials.GCMonitorExample$2$1@b02efa with reference java.lang.ref.PhantomReference@dc57db
Object that was wrapped by java.lang.ref.PhantomReference@1bc82e7 was gc'ed
Object that was wrapped by java.lang.ref.PhantomReference@73a34b was gc'ed
Object that was wrapped by java.lang.ref.PhantomReference@9ced8e was gc'ed
Wrapped de.tutorials.GCMonitorExample$2$1@c24c0 with reference java.lang.ref.PhantomReference@140c281
Wrapped de.tutorials.GCMonitorExample$2$1@a1d1f4 with reference java.lang.ref.PhantomReference@1df280b
Wrapped de.tutorials.GCMonitorExample$2$1@1be0f0a with reference java.lang.ref.PhantomReference@cd5f8b
Object that was wrapped by java.lang.ref.PhantomReference@1df280b was gc'ed
Object that was wrapped by java.lang.ref.PhantomReference@dc57db was gc'ed
Object that was wrapped by java.lang.ref.PhantomReference@140c281 was gc'ed
Wrapped de.tutorials.GCMonitorExample$2$1@13bd574 with reference java.lang.ref.PhantomReference@13adc56
Wrapped de.tutorials.GCMonitorExample$2$1@157aa53 with reference java.lang.ref.PhantomReference@6f50a8
Object that was wrapped by java.lang.ref.PhantomReference@cd5f8b was gc'ed
Wrapped de.tutorials.GCMonitorExample$2$1@187814 with reference java.lang.ref.PhantomReference@73a7ab
Object that was wrapped by java.lang.ref.PhantomReference@13adc56 was gc'ed
Object that was wrapped by java.lang.ref.PhantomReference@6f50a8 was gc'ed
Wrapped de.tutorials.GCMonitorExample$2$1@104faf8 with reference java.lang.ref.PhantomReference@1f8c6df
Wrapped de.tutorials.GCMonitorExample$2$1@1c86be5 with reference java.lang.ref.PhantomReference@123b25c
Object that was wrapped by java.lang.ref.PhantomReference@123b25c was gc'ed
Object that was wrapped by java.lang.ref.PhantomReference@73a7ab was gc'ed
Wrapped de.tutorials.GCMonitorExample$2$1@92bbba with reference java.lang.ref.PhantomReference@162dbb6
Object that was wrapped by java.lang.ref.PhantomReference@1f8c6df was gc'ed
Wrapped de.tutorials.GCMonitorExample$2$1@146c1d4 with reference java.lang.ref.PhantomReference@5f8172
Wrapped de.tutorials.GCMonitorExample$2$1@2ba11b with reference java.lang.ref.PhantomReference@5e179a
Gruß Tom
 
Mh, so ganz verstehe ich den Unterschied glaube ich noch nicht. Wenn die Instanz hinter der WeakReference vom GC aufgesammelt wurde, ist doch ebenfalls das Objekt verschwunden oder?
 
Hallo!

Jo, wenn get() an einer WeakReference null zurueckgibt ist die einst davon gewrappedte Instanz gc'ed worden. Es kommt eben drauf an wie man auf das gc'ed werden von Instanzen reagieren moechte. Will man das "direkt" mitbekommen, so bietet sich die PhantomReference in Verbindung mit einer gepollten ReferenceQueue an. Will man es jedoch indirekt ( bie Objektzugriff testen) mitbekommen kann man auch den Weg ueber WeakReferences gehen.
Da aber unser eigentliches Theme "Speicherprobleme in den Griff kriegen" lautet passt hier meiner Meinung nach die PhantomReference Variante besser, da man hier so frueh wie moeglich Informationen ueber gc'edte Instanzen bekommt.

Gruss Tom
 
Zurück