Advanced Java Services | Weak References |
Referenzen der folgenden Art
StringBuilder sb = new StringBuilder(); StringBuilder sb2 = sb; // zweite Referenz auf das StringBuilderObjekt sb = null; // nur noch eine Referenz auf das Objekt; //... sb2 = null; // keine Referenz mehr auf das Objekt
wollen wir in diesem Kapitel starke Referenzen nennen. Sie referenzieren ein Objekt im Hauptspeicher. In C++ würde man einfach sagen 'sb ist ein Pointer'. Findet der GarbageCollector keine Referenz mehr auf dieses Objekt so löscht er es. In der obigen Situation ist das (irgendwann) nach dem letzten Statement der Fall.
Neben der starken Referenz gibt es in Java drei abgeschwächte Referenzen. Sie leiten sich alle von der abstrakten Klasse java.lang.ref.Reference<T> ab und befinden sich im selben Package.
Sie sind oben von schwach nach noch schwächer geordnet. Da Phantomreferenzen selten gebraucht werden, wird hier das Verhalten von soften und schwachen Referenzen gezeigt. Zunächst wir kein Objekt gelöscht, auf das es noch eine starke Referenz gibt. Gibt es nach dem Verschwinden der starken Referenzen noch schwache Referenzen, so entscheidet der GC wann auch diese gelöscht werden.
Eine SoftReference wird vom GC dann gelöscht, wenn der Speicher knapp wird. So verbessern etwa speicheraufwendige Graphikobjekte die Performance eines Programms, aber wenn der Speicher knapp wird kann man nicht verwendete Objekte löschen und bei Bedarf nachladen. Mit SoftReferences kann man genau dieses Verhalten codieren. Das folgende kleine Beispiel legt eine starke und eine softe Referenz an. Nach dem Löschen der starken Referenz bleibt die SoftReference zunächst bestehen. Erst wenn der Speicher knapp wird reagiert der GC.
private static void softReferenceDemo1()
{
Object ob = new Object(); // starke Referenz
SoftReference<Object> sr = new SoftReference<>(ob); // Softreferenz auf ob
System.out.println("Starke Referenz : " + ob);
System.out.println("Softe Referenz: " + sr.get());
System.out.println("Starke Referenz löschen");
ob = null;
System.out.println("Softe Referenz: " + sr.get());
System.out.println("GarbageCollector anstoßen");
System.gc();
try { Thread.sleep(2000); } catch(InterruptedException ex) {}
System.out.println("softe Referenz nach 2 Sekunden: " + sr.get());
// wird erst gelöscht, wenn speicher anderweitig gebraucht wird
System.out.println("Großes int-array anschaffen");
int[] arr = new int[44687000]; // Größe rechnerabhängig!
//System.gc();
try { Thread.sleep(1000); } catch(InterruptedException ex) {}
System.out.println("Softe Referenz danach: " + sr.get());
}
Hier die Ausgabe des Programms:
Starke Referenz : java.lang.Object@1db9742 Softe Referenz: java.lang.Object@1db9742 Starke Referenz löschen Softe Referenz: java.lang.Object@1db9742 GarbageCollector anstoßen Softe Referenz nach 2 Sekunden: java.lang.Object@1db9742 Großes int-array anschaffen Softe Referenz danach: null
Eine schwache Referenz wird dagegen vom GC gelöscht, wenn es keine starke Referenz mehr auf das Objekt gibt. Meist hilft hier ein Anstoßen des GC mit der Methode System.gc(). Wie alle schwachen Referenzen ist auch die WeakReference typisiert. das folgende Beispiel zeigt das verhalten.
/**
* Erzeugen einer WeakReference
* Verhalten einer WeakReference
* nach dem Löschen der starken Referenz wird die schwache Referenz durch den GC gelöscht
*/
private static void weakReferenceDemo1()
{
Object ob = new Object(); // starke Referenz
WeakReference<Object> wr = new WeakReference<>(ob); // schwache Referenz auf ob
System.out.println("starke Referenz : " + ob);
System.out.println("schwache Referenz: " + wr.get());
System.out.println("starke Referenz löschen");
ob = null;
System.out.println("schwache Referenz: " + wr.get());
System.out.println("GarbageCollector anstoßen");
System.gc();
System.out.println("schwache Referenz: " + wr.get());
}
das Programm macht die folgende Ausgabe:
Starke Referenz : java.lang.Object@1db9742 Softe Referenz: java.lang.Object@1db9742 Starke Referenz löschen Softe Referenz: java.lang.Object@1db9742 GarbageCollector anstoßen Softe Referenz nach 2 Sekunden: java.lang.Object@1db9742 Großes int-array anschaffen Softe Referenz danach: null
Mit einer ReferenceQueue kann der Entwickler erkennen, wann der GC eine schwache Referenz gelöscht hat. Übergibt man dem Konstruktor beim Anlegen einer schwachen Referenz eine (starke) Referenz auf eine ReferenceQueue, so legt der GC nach dem Löschen des Objekts die schwache Referenz in der ReferenceQueue ab. Landet die schwache Referenz in der Queue, so weiß man, daß das Objekt gelöscht wurde. das Objekt ist dann nicht mehr verfügbar, ein Aufruf von get() liefert null. das folgende Beispiel zeigt dieses Verhalten für eine schwache Referenz.
/** * WeakReference und ReferenceQueue * Eine schwache Referenz wird in die ReferenceQueue aufgenommen nachdem die * starke Referenz gelöscht wurde. Sie ist dann auch über eine schwache * Referenz nicht mehr erreichbar. */ private static void weakReferenceQueueDemo() { Object ob = new Object(); // starke Referenz ReferenceQueue<Object> refQueue = new ReferenceQueue<>(); WeakReference<Object> wr = new WeakReference<>(ob, refQueue) ; // schwache Referenz System.out.println("starke Referenz : " + ob); System.out.println("schwache Referenz: " + wr.get()); System.out.println("poll referenceQueue: " + refQueue.poll()); //null System.out.println("=== starke Referenz löschen ==="); ob = null; System.out.println("schwache Referenz: " + wr.get()); System.out.println("GarbageCollector anstoßen"); System.gc(); try { Thread.sleep(1000); } catch(InterruptedException ex) {} System.out.println("schwache Referenz: " + wr.get()); Reference<? extends Object> polledReference = refQueue.poll(); System.out.println("poll referenceQueue : " + polledReference); // java.lang.ref.WeakReference@c3c749 System.out.println("polled reference get: " + polledReference.get()); // java.lang.ref.WeakReference@c3c749 System.out.println("poll referenceQueue again: " + refQueue.poll()); //null }
Die Ausgabe
starke Referenz : java.lang.Object@1db9742 schwache Referenz: java.lang.Object@1db9742 poll referenceQueue: null === starke Referenz löschen === schwache Referenz: java.lang.Object@1db9742 GarbageCollector anstoßen schwache Referenz: null poll referenceQueue : java.lang.ref.WeakReference@106d69c polled reference get: null poll referenceQueue again: null
Beim Arbeiten mit einer HashMap kann es vorkommen, daß ein Key gelöscht wird, bevor der entsprechende Eintrag in der Hashmap gelöscht wird. Das (Key, Value) paar ist dann nicht mehr zu entfernen, da der Key verloren ist. Hier kann man schwache Referenzen einsetzen. Im folgenden Beispiel verwenden wir eine HashMap, deren Keys schwache Referenzen sind. Wird ein Key gelöscht, so liefert ein get() auf eine schwache Referenz null. Da man die schwache Referenz aber noch erreichen kann, kann man die Einträge in der HashMap löschen programmatisch löschen. Das folgende Beispiel demonstriert das.
/** * A HashMap with WeakReferences as keys * die muß man selbst durchlaufen und löschen, falls die schwachen Referenzen auf null zeigen */ private static void weakHashMapDemo1() throws InterruptedException { HashMap<WeakReference<Object>, String> whm = new HashMap<>(); Object key1 = new Object(); Object key2 = new Object(); Object key3 = new Object(); whm.put(new WeakReference<>(key1), "Storm"); whm.put(new WeakReference<>(key2), "Wedekind"); whm.put(new WeakReference<>(key3), "Mann"); System.out.println("size = " + whm.size()); key2 = null; System.out.println("size = " + whm.size()); System.gc(); TimeUnit.SECONDS.sleep(1); System.out.println("size = " + whm.size()); Set<WeakReference<Object>> keys = whm.keySet(); System.out.println("keyset:"); for( WeakReference<Object> key: keys) { System.out.println(key.get()); } WeakReference<Object>[] keyArr = keys.toArray(new WeakReference[0]); for(WeakReference<Object> key : keyArr) { if (key.get() == null) { whm.remove(key); } } System.out.println("size = " + whm.size()); }
Die Ausgabe des Programms:
size = 3 size = 3 size = 3 keyset: java.lang.Object@25154f java.lang.Object@10dea4e null size = 2
Angenehmerweise gibt es eine spezielle HashMap, die das Löschen der Keys automatisch erledigt: Weakhashmap. Damit vereinfacht sich das vorige Beispiel. hier das vereinfachte Programm.
/** */ private static void weakHashMapDemo2() throws InterruptedException { WeakHashMap<Object, String> whm = new WeakHashMap<>(); Object key1 = new Object(); Object key2 = new Object(); Object key3 = new Object(); whm.put(key1, "Storm"); whm.put(key2, "Wedekind"); whm.put(key3, "Mann"); System.out.println("size = " + whm.size()); key2 = null; System.out.println("size = " + whm.size()); System.gc(); TimeUnit.SECONDS.sleep(2); System.out.println("size = " + whm.size()); }
Die Ausgabe
size = 3 size = 3 size = 3 keyset: java.lang.Object@25154f java.lang.Object@10dea4e null size = 2