Swing garbage collection – problems with the observer pattern
Clearly the observer pattern is very important in Swing programming, it is used every time you register a Swing model with a UI component, so that the component can update itself as the model data changes. However, there are inherent dangers in using this pattern without considering garbage collection.
The problem is that most standard implementations of swing models hold strong references to the listeners which register with them. This prevents the listeners from being garbage collected, until the model itself becomes available to the garbage collector. Some important models in an application may have a long expected life time (they may be created when the application starts up and dereferenced only when it exits). If these models hold on to all their listeners, some of which may be temporary screens or dialogs which the user opens and closes, the impact on memory usage can be far-reaching. This issue has been at the core of the most of the severe memory management issues I have seen so far with Swing).
Most solutions to the problem suggest that the correct approach to solving it would be to modify the models in order to store the listener objects as weak references.
Upgrading models to use weak references
This approach works, but has at least one severe drawback – although you can use the techique freely in your own models, the standard models which Swing provides do not use weak references. This means that you will probably need to extend them, and then modify your code to use the extended versions of the models, or implement a totally redesigned model class. This is non-trivial, and will involve careful analysis of the way the existing models access and update their listener lists. Furthermore, it will be necessary to check the implementation of the models on subsequent updates to the swing core libraries.
Additionally, fixing this problem by updating the models has another drawback – it is inflexible. In most implementations the ‘weak reference model’ wraps all of its registered listeners in a weak reference indescriminately. There may be occasions when you don’t want this to be the case – some listeners may still be functionally important even though they have no references from elsewhere in the application, and you don’t want these to be garbage collected. However there is no way for client classes to opt out when they add their listeners – all listeners added to the ‘weak reference models’ will be held using weak references. There may be workaround to this, but overall it adds to the potential problems.
WeakReferenceListener wrapper class
A preferable solution is to provide a way for the client classes to decide whether weak references are required at the point they add a listener to the model. The following techniques can be used without modifying the model classes at all.
Both techniques below involve wrapping the real listener instance using a weak reference wrapper class. The wrapper class is added to the model, where it acts as a proxy for the real listener instance. The weak reference proxy first tests whether the real listener has been garbage collected, before delegating event method calls.
import java.awt.event.ActionEvent;
import java.lang.ref.WeakReference;
/**
* WeakRefActionListener delegates the handling of ActionEvents to an ActionListener wrapped in a weak reference
*/
public class WeakRefActionListener implements ActionListener
{
private WeakReference<ActionListener> actionListenerDelegate;
public WeakRefActionListener(ActionListener actionListener)
{
this.actionListenerDelegate = new WeakReference<ActionListener>(actionListener);
}
public void actionPerformed(ActionEvent e)
{
ActionListener delegate = actionListenerDelegate.get();
if ( delegate != null )
{
delegate.actionPerformed(e);
}
}
}
The WeakRefActionListener can be used in the following manner
{
public void actionPerformed(ActionEvent e)
{
System.out.println("Button clicked!");
}
}
button.addActionListener(
new WeakRefActionListener( actionListener )
);
There are two things to note:
- The client class must keep a reference to the listener it creates. It will not work with anonymous listeners. Unless the client class or another object instance holds a reference to the listener, the listener will be eligable for garbage collection immediately once its weak reference proxy has been added to the model.
- The model will still retain a reference to the weak reference proxy, even when the wrapped listener instance has been garbage collected. This will not create any problems unless many such listeners are added to the model, but in this case performance could degrade as the model iterates through a list of weak reference listeners which are mostly no longer valid. A possible solution to this might make use of a background thread to clean up the invalid WeakRef listeners
You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
February 23rd, 2009 at 2:34 pm
There is a slight bug in your implementation of WeakRefActionListener.actionPerformed(), in that the reference may be nulled in between the two calls to get().
To fix it, hold a local strong reference from a single call:
public void actionPerformed(ActionEvent e)
{
ActionListener delegate = actionListenerDelegate.get();
if (delegate != null)
{
delegate.actionPerformed(e);
}
}
February 23rd, 2009 at 2:54 pm
Bez,
You were spot on there, how did I let that one slip through?
Thanks for finding that and pointing it out, I have revised the example
thanks
Nick