|
This Tech Tip reprinted with permission by java.sun.com
The May 11, 1999 Tech Tip titled Reference
Objects
introduced the concept of reference objects, but didn't go into much
depth. In this tip, you'll learn more about this topic. Basically,
reference objects provide a way to indirectly reference the memory
needed by objects. The reference objects are maintained in a reference
queue (class ReferenceQueue), which monitors
the
reference objects for reachability. Based on the type of reference
object, the garbage collector can free memory at times when a normal
object reference might not be released.
In the Java platform, there are four types of references to
objects.
Direct references are the type you normally use, as in:
Object obj = new Object()
|
You can think of direct references as strong references that
require no extra coding to create or access the object. The remaining
three types of references are subclasses of the
Reference
class found in the java.lang.ref package.
Soft references are provided by the SoftReference
class, weak references by the WeakReference
class, and phantom references by PhantomReference.
Soft references act like a data cache. When system memory is low, the
garbage collector can arbitrarily free an object whose only reference
is a soft reference. In other words, if there are no strong references
to an object, that object is a candidate for release. The garbage
collector is required to release any soft references before throwing an
OutOfMemoryException.
Weak references are weaker than soft references. If the only references
to an object are weak references, the garbage collector can reclaim the
memory used by an object at any time. There is no requirement for a low
memory situation. Typically, memory used by the object is reclaimed in
the next pass of the garbage collector.
Phantom references relate to cleanup tasks. They offer a
notification immediately before the garbage collector performs the
finalization process and frees an object. Consider it a way to do
cleanup tasks within an object.
As a simple demonstration of using a WeakHashMap,
the following WeakTest program creates a WeakHashMap
with a single element in it. It then creates a second thread that waits
for the map to empty out, requesting that the garbage collector run
every 1/2 second. The main thread waits for this second thread to
finish.
import java.util.*;
public class WeakTest {
private static Map<String, String> map;
public static void main (String args[]) {
map = new WeakHashMap<String, String>();
map.put(new String("Scott"), "McNealey");
Runnable runner = new Runnable() {
public void run() {
while (map.containsKey("Scott")) {
try {
Thread.sleep(500);
} catch (InterruptedException ignored) {
}
System.out.println("Checking for empty");
System.gc();
}
}
};
Thread t = new Thread(runner);
t.start();
System.out.println("Main joining");
try {
t.join();
} catch (InterruptedException ignored) {
}
}
}
|
Since there are no strong references to the only key in the
map,
the entry in the map should be garbage collected at the earliest
convenience of the system.
Running the program generates the following results:
Main joining Checking for empty
There are two important things to point out in this example. First,
normally a call to System.gc() would not be
required. Because the WeakTest
program is lightweight and doesn't use much memory an explicit call to
the garbage collector is necessary. Second, notice the call to new
String("Scott"). You might ask why call new
String() when the end result is the same "Scott"
string? The answer is that if you don't call new String(),
the reference for the map key will be to the system's string constant
pool. This never goes away, so the weak reference will never be
released. To get around this, new String("Scott")
creates
a reference to the reference in the string constant pool. The string
contents are never duplicated. They stay in the constant pool. This
simply creates a separate pointer to the string constant in the pool.
A more complicated use of WeakHashMap is to
manage a listener list for a Swing component or data model. This would
not be a general purpose listener list. Instead it would be a weak
listener list. In this case, as long as a strong reference is kept
outside the component or data model, that object will continue to
notify the listener/observer. This helps garbage collection in the
sense that the listener doesn't prevent the source object (which
registered the listener) from being garbage collected. By default,
listener references are strong references and are not garbage collected
when your method returns. You must remember to remove the listener when
you finish monitoring the situation.
To demonstrate, lets create a WeakListModel
that offers a ListModel. The ListModel
relies on a WeakHashMap for storing ListDataListener
objects.
Aside from the imports, the start of the class definition
comprises the
class and local variable declarations.
public class WeakListModel implements ListModel,
Serializable {
private Map<ListDataListener, Object> listenerList =
Collections.synchronizedMap(
new WeakHashMap<ListDataListener, Object>());
private final Object present = new Object();
private ArrayList<Object> delegate = new ArrayList<Object>();
|
The listener list is a WeakHashMap.
Its
access
is synchronized. Although some implementations of listener lists rely
on making copies to avoid synchronized access, weak references can be
removed at any time. So making a copy provides two places for the weak
reference to be dropped with no added value.
Because the listener list is maintained in a Map,
you need both a key and a value. The value associated with every key in
the map is the 'present' object.
Theoretically, you only need a WeakHashSet.
However, the class is not in the predefined set of collections, so a WeakHashMap
is used instead.
The last variable, delegate, manages the ListModel
contents. Most of the ListModel interface
implementation simply passes the request to the delegate
to perform the operation, hence the name delegate.
The first set of methods for the WeakListModel
are related to sizing or querying the model structure. In all cases,
these pass the call to the delegate:
public int getSize() {
return delegate.size();
}
public Object getElementAt(int index) {
return delegate.get(index);
}
public void trimToSize() {
delegate.trimToSize();
}
public void ensureCapacity(int minCapacity) {
delegate.ensureCapacity(minCapacity);
}
public int size() {
return delegate.size();
}
public boolean isEmpty() {
return delegate.isEmpty();
}
public Enumeration elements() {
return Collections.enumeration(delegate);
}
public boolean contains(Object elem) {
return delegate.contains(elem);
}
public int indexOf(Object elem) {
return delegate.indexOf(elem);
}
public int lastIndexOf(Object elem) {
return delegate.lastIndexOf(elem);
}
public Object elementAt(int index) {
return delegate.get(index);
}
public Object firstElement() {
return delegate.get(0);
}
public Object lastElement() {
return delegate.get(delegate.size()-1);
}
public String toString() {
return delegate.toString();
}
|
The next set of methods have to do with adding and removing
elements.
In addition to accessing the delegate for the
storage operation, the set of listeners need to be notified. These
methods call fireXXX()
methods that will be shown shortly. These methods do the bulk of the
work in the class.
public void setElementAt(Object obj, int index) {
delegate.set(index, obj);
fireContentsChanged(this, index, index);
}
public void removeElementAt(int index) {
delegate.remove(index);
fireIntervalRemoved(this, index, index);
}
public void insertElementAt(Object obj, int index) {
delegate.add(index, obj);
fireIntervalAdded(this, index, index);
}
public void addElement(Object obj) {
int index = delegate.size();
delegate.add(obj);
fireIntervalAdded(this, index, index);
}
public boolean removeElement(Object obj) {
int index = indexOf(obj);
boolean rv = delegate.remove(obj);
if (index >= 0) {
fireIntervalRemoved(this, index, index);
}
return rv;
}
public void removeAllElements() {
int index1 = delegate.size()-1;
delegate.clear();
if (index1 >= 0) {
fireIntervalRemoved(this, 0, index1);
}
}
|
The listener list itself deals with the add and remove
listener calls.
Again, the present object is not used itself.
A "map" just needs a key and value. This is the same pattern used by TreeSet,
which is backed by a TreeMap.
public synchronized void addListDataListener(
ListDataListener l) {
listenerList.put(l, present);
}
public synchronized void removeListDataListener(
ListDataListener l) {
listenerList.remove(l);
}
public EventListener[] getListeners(Class listenerType) {
Set<ListDataListener> set = listenerList.keySet();
return set.toArray(new EventListener[0]);
}
|
The most interesting set of methods are the fireXXX()
methods. For all three of these methods, fireContentsChanged(),
fireIntervalAdded(), and fireIntervalRemoved(),
a new Set is created with the keys from the WeakHashMap.
This is done to ensure that if the set changes in the middle of
notification, the original set is still notified.
protected synchronized void fireContentsChanged(
Object source, int index0, int index1) {
ListDataEvent e = null;
Set<ListDataListener> set =
new HashSet<ListDataListener>(listenerList.keySet());
Iterator<ListDataListener> iter = set.iterator();
while (iter.hasNext()) {
if (e == null) {
e = new ListDataEvent(
source, ListDataEvent.CONTENTS_CHANGED,
index0, index1);
}
ListDataListener ldl = iter.next();
ldl.contentsChanged(e);
}
}
protected synchronized void fireIntervalAdded(
Object source, int index0, int index1) {
ListDataEvent e = null;
Set<ListDataListener> set =
new HashSet<ListDataListener>(listenerList.keySet());
Iterator<ListDataListener> iter = set.iterator();
while (iter.hasNext()) {
if (e == null) {
e = new ListDataEvent(
source, ListDataEvent.INTERVAL_ADDED,
index0, index1);
}
ListDataListener ldl = iter.next();
ldl.intervalAdded(e);
}
}
protected synchronized void fireIntervalRemoved(
Object source, int index0, int index1) {
ListDataEvent e = null;
Set<ListDataListener> set =
new HashSet<ListDataListener>(listenerList.keySet());
Iterator<ListDataListener> iter = set.iterator();
while (iter.hasNext()) {
if (e == null) {
e = new ListDataEvent(
source, ListDataEvent.INTERVAL_REMOVED,
index0, index1);
}
ListDataListener ldl = iter.next();
ldl.intervalRemoved(e);
}
}
|
Add the imports and close the braces and you have your
class definition:
import java.io.Serializable;
import java.util.*;
import java.lang.ref.*;
import javax.swing.*;
import javax.swing.event.*;
...
}
|
To test the ListModel, the following
program,
TestListModel, defines a ListDataListener
and associates it with the created WeakListModel.
Notice that adding and removing the names of Sun's founders as elements
to the model notifies the listener. As soon as the strong reference to
the listener is gone, the WeakListModel can
release the weak listener reference. Further operations on the model
then do not notify the listener.
import javax.swing.*;
import javax.swing.event.*;
public class TestListModel {
public static void main (String args[]) {
ListDataListener ldl = new ListDataListener() {
public void intervalAdded(ListDataEvent e) {
System.out.println("Added: " + e);
}
public void intervalRemoved(ListDataEvent e) {
System.out.println("Removed: " + e);
}
public void contentsChanged(ListDataEvent e) {
System.out.println("Changed: " + e);
}
};
WeakListModel model = new WeakListModel();
model.addListDataListener(ldl);
model.addElement("Scott McNealy");
model.addElement("Bill Joy");
model.addElement("Andy Bechtolsheim");
model.addElement("Vinod Khosla");
model.removeElement("Scott McNealy");
ldl = null;
System.gc();
model.addElement("Scott McNealy");
System.out.println(model);
}
}
|
Compile and run the TestListModel
program. It
should generate the following results:
> java TestListModel
Added: javax.swing.event.ListDataEvent[type=1,index0=0,index1 =0] Added: javax.swing.event.ListDataEvent[type=1,index0=1,index1 =1] Added: javax.swing.event.ListDataEvent[type=1,index0=2,index1 =2] Added: javax.swing.event.ListDataEvent[type=1,index0=3,index1 =3] Removed: javax.swing.event.ListDataEvent[type=2,index0=0,inde x1=0] [Bill Joy, Andy Bechtolsheim, Vinod Khosla, Scott McNealy]
See the java.lang.ref
package documentation for additional information about
reference objects, notification, and reachability.
Copyright (c) 2004-2005 Sun Microsystems, Inc.
All Rights Reserved.
Related Tips
|