4.11.6 | Referenzklassen |
Bis zum JDK 1.1 konnten Objekte in Java nur mit starken Referenzen referenziert werden. Schwache Referenzen, wie es sie beispielsweise auch in Smalltalk gibt, fehlten bislang. Diese Lücke wurde im JDK 1.2 durch die Referenzklassen im neuen Paket java.lang.ref geschlossen.
Die bisher bekannten Verweise (Referenzen) auf Objekte nennt man auch starke Referenzen, weil sie den Garbage Collector daran hindern, das referenzierte Objekt zu entfernen. Im Gegensatz dazu verhindern schwache Referenzen die Freigabe des Objekts nicht.
Anwendungsfälle für schwache Referenzen sind unter anderem:Schwache Referenzen werden von Klassen dargestellt, die alle von der abstrakten Klasse java.lang.ref.Reference abgeleitet sind. Diese Klassen werden im folgenden Referenzklassen genannt. Eine schwache Referenz auf ein Objekt wird erzeugt, in dem eine starke Referenz darauf im Konstruktor einer Referenzklasse übergeben wird:
- Zuordnungstabellen mit Schlüssel-Wert-Paaren, deren Einträge automatisch entfernt werden können, wenn das Schlüsselobjekt nicht mehr existiert
- Abschlussaktionen verschiedenster Art, für die das Konzept der Finalisierung mittels der Methode finalize() nicht ausreicht
- Die Realisierung von Objekt-Caches
Object anObject = new Object(); WeakReference ref = new WeakReference(anObject);So erzeugte Referenzobjekte kapseln also immer eine schwache Referenz auf ein bestimmtes Objekt. Alle Unterklassen von Reference verfügen über die geerbten Methoden get(), mit der das referenzierte Objekt abgerufen werden kann, sowie clear(), mit der die schwache Referenz gelöscht wird. Neben WeakReference gibt es noch die Klasssen PhantomReference und SoftReference.
Mit einer zweiten Konstruktorvariante können Referenzobjekte zusätzlich einer Warteschlange zugeordnet werden. Diese Warteschlangen werden von der Klasse ReferenceQueue repräsentiert:Object anObject = new Object(); ReferenceQueue queue = new ReferenceQueue(); WeakReference ref = new WeakReference(anObject, queue);In diesem Fall wird das Referenzobjekt in seine Warteschlange eingetragen, wenn das referenzierte Objekt zur Beseitigung ansteht. Durch Abfragen dieser Warteschlange (z.B. in einem eigenen Thread) kann ein Programm die erforderlichen Abschlussaktionen durchführen. Zu diesem Zweck stellt ReferenceQueue die Methode remove() zur Verfügung. Sie entfernt das erste Referenzobjekt in der Warteschlange und liefert es zurück. Wenn die Schlange momentan leer ist, blockiert sie so lange, bis der Garbage Collector ein neues Referenzobjekt einträgt. Daneben gibt es noch die nicht blockierende Methode poll(), die keinen eigenen Thread erfordert. Falls sie eine leere Schlange vorfindet, kehrt sie sofort zurück und liefert einfach null.
Durch die Einführung der Referenzklassen werden nunmehr vier verschiedene Arten von Referenzen unterschieden. Nachfolgend sind die einzelnen Typen vom stärksten zum schwächsten Typ aufgeführt:
- Die starken Referenzen. Solange es mindestens eine solche Referenz gibt, heißt ein Objekt stark referenziert.
- Die Klasse SoftReference stellt einen Referenztyp zur Verfügung, der sich besonders dazu eignet, Objekt-Caches zu implementieren.
Objekte, die ausschließlich von SoftReference-Objekten referenziert werden, bleiben so lange von der Garbage Collection verschont, wie der Hauptspeicher ausreicht. Wenn der Speicher knapp wird, werden die referenzierten Objekte entfernt und die Soft-Referenzen automatisch gelöscht.
Mit Referenzen dieses Types können Objekte also gewissermaßen auf Vorrat gehalten werden, solange die Systemressourcen es erlauben.
Wenn das referenzierte Objekt wieder benötigt wird, kann man es mit der Methode get() erneut abrufen. Falls das Objekt in der Zwischenzeit mangels Speicher abgeräumt wurde, liefert get() null, was man bei der Implementierung von Caches entsprechend berücksichtigen muss (siehe Beispiel).
- Die nächste Abstufung sind die schwachen Referenzen. Sie werden von der Klasse WeakReference dargestellt. Ein Objekt ist schwach referenziert, wenn es weder stark noch »soft« referenziert wird. In diesem Fall werden alle schwachen Referenzen gelöscht, und das Objekt wird zur Finalisierung und anschließenden Entfernung freigegeben.
Das klassische Beispiel für den Einsatz von WeakReference ist eine Abbildungstabelle, die einem Schlüsselobjekt ein anderes Objekt zuordnet und dennoch das Entfernen der Schlüsselobjekte durch den Garbage Collector gestattet. Dieser Anwendungsfall ist so häufig, dass das JDK 1.2 eine fertige Implementierung hierfür bietet: java.util.WeakHashMap.
Die Schnittstelle dieser Klasse ist der bekannten Hashtable sehr ähnlich. In der Implementierung der Methode put(key, value) werden aber nicht die Schlüsselobjekte selbst, sondern WeakReference-Objekte auf die Schlüssel gespeichert. Diese Referenzobjekte werden hierbei einer internen Warteschlange zugeordnet, die bei jedem Aufruf von put(), remove() und clear() abgefragt wird, um alte Einträge zu entfernen. Die Implementierung bedient sich dabei der oben erwähnten Methode poll() der Klasse ReferenceQueue, wie das folgende Fragment aus dem Quelltext von java.util.WeakHashMap zeigt:
WeakKey wk; while ((wk = (WeakKey)queue.poll()) != null) { hash.remove(wk); }hash ist die Tabelle, in der die Einträge intern gespeichert werden. WeakKey ist eine intern in WeakHashMap definierte Unterklasse von WeakReference. Die WeakKey-Objekte wurde bei ihrer Erzeugung der Warteschlange queue zugeordnet.- Der schwächste Typ sind schließlich die Phantom-Referenzen, für die es die Klasse PhantomReference gibt. Ein Objekt heißt Phantom-referenziert, wenn es nicht mehr schwach referenziert und bereits finalisiert wurde. Wenn es keine Phantom-Referenz mehr gibt, wird das Objekt endgültig entfernt und unerreichbar.
Ein Phantom-referenziertes Objekt ist aus Sicht der Anwendung nicht mehr benutzbar, weil es bereits finalisiert wurde. Daher ist die Methode get() so überschrieben, dass sie grundsätzlich null liefert.
Phantom-Referenzen erlauben somit nur Abschlussaktionen allgemeiner Art, die sich nicht auf ein bestimmtes Exemplar beziehen, weil man lediglich feststellen kann, dass ein Objekt finalisiert wurde. Man kann weder ermitteln, um welches konkrete Objekt es sich handelt, noch, von welcher Klasse es war.
SoftReference-Objekte können beispielsweise in einem Applet verwendet werden, das Datensätze eines Produktkatalogs von einem Datenbankserver bezieht. Da die Übertragung über das Netz insbesondere bei großen Datensätzen einen erheblichen Teil der Antwortzeit ausmacht, lohnt es sich, die bereits abgerufenen Datensätze in einem Cache zu halten, solange der verbleibende Hauptspeicher des Clients groß genug ist. Wurde die Cache-Referenz dagegen aufgrund geringen Hauptspeichers in der Zwischenzeit wieder gelöscht, muss der benötigte Datensatz eben wieder vom Server geholt werden.
Der folgende Auszug zeigt, wie man einen Objekt-Cache unter Verwendung der Klasse SoftReference implementieren kann. Beim Ablegen eines Objekts im Cache wird ein SoftReference-Objekt in einer Hashtable abgelegt:cache = new Hashtable(); ... void store(Record record) { cache.put(new Integer(record.number), new SoftReference(record)); }Beim Abrufen eines Objekts aus dem Cache muss zunächst festgestellt werden, ob das Objekt im Cache vorhanden ist (Cache Hit) oder nicht (Cache Miss). Ein Cache Miss liegt vor, wenn der Schlüssel nicht in der Tabelle vorhanden ist, aber auch dann, wenn der Schlüssel zwar existiert, aber das referenzierte Objekt in der Zwischenzeit vom Garbage Collector entfernt wurde. Deswegen muss nach dem Abrufen des Referenzobjekts zusätzlich geprüft werden, ob dessen get()-Methode null liefert.Record retrieve(int key) { SoftReference ref; ref = (SoftReference)cache.get(new Integer(key)); if (ref == null) // Cache miss return null; else { // Cache hit Record rec; rec = (Record)ref.get(); // Objekt mittlerweile entfernt ? if (rec == null) { // Eintrag löschen cache.remove(new Integer(key)); } return rec; } }Das komplette Beispiel befindet sich auf der CD und besteht aus einem Server, der die Daten bereitstellt und dem in Abbildung 4.6 dargestellten Applet, das die Daten über eine Socket-Verbindung von diesem Server holt. Im Eingabefeld kann eine Artikelnummer eingegeben werden, z. B. »1«. Durch Drücken des Buttons wird der dazugehörige Datensatz geholt, und zwar beim ersten Mal vom Server, bei weiteren Zugriffen aus dem Cache. Die Quelle, aus der das Objekt bezogen wurde, wird am unteren Rand angezeigt.
Material zum Beispiel
- Quelltexte: