java left logo
java middle logo
java right logo
 

Home arrow Java SE Tips
 
 
Main Menu
Home
Java Tutorials
Book Reviews
Java SE Tips
Java ME Tips
Java EE Tips
Other API Tips
Java Applications
Java Libraries
Java Games
Java Network
Java Forums
Java Blog




Most Visited Tips
Java SE Tips
Java ME Tips
Java EE Tips
Other API Tips
Java Applications
Java Libraries
Java Games
Book Reviews
Top Rated Tips
Java SE Tips
Java ME Tips
Java EE Tips
Other API Tips
Java Applications
Java Libraries
Java Games
Book Reviews


Statistics
Registered Users: 3947
Java SE Tips: 614
Java ME Tips: 202
Java EE Tips: 183
Other API Tips: 779
Java Applications: 298
Java Libraries: 209
Java Games: 16
Book Reviews:
 
 
 
Using WeakHashMap for Listener Lists E-mail
User Rating: / 30
PoorBest 

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

 
< Prev   Next >
 
       
         
     
 
 
 
   
 
 
java bottom left
java bottom middle
java bottom right
RSS 0.91 FeedRSS 1.0 FeedRSS 2.0 FeedATOM FeedOPML Feed

Home - About Us - Privacy Policy
Copyright 2005 - 2008 www.java-tips.org
Java is a trademark of Sun Microsystems, Inc.