Pros and Cons of Listeners as WeakReferences

JavaDesign PatternsObserver PatternWeak References

Java Problem Overview


What are the pros and cons of keeping listeners as WeakReferences?

The big 'Pro' of course is that:

Adding a listener as a WeakReference means the listener doesn't need to bother 'removing' itself.

For those worried about the listener having the only reference to the object, why can't there be 2 methods, addListener() and addWeakRefListener()?

Those who don't care about removal can use the latter.

Java Solutions


Solution 1 - Java

First of all, using WeakReference in listeners lists will give your object different semantic, then using hard references. In hard-reference case addListener(...) means "notify supplied object about specific event(s) until I stop it explicitly with removeListener(..)", in weak-reference case it means "notify supplied object about specific event(s) until this object will not be used by anybody else (or explicitly stop with removeListener)". Notice, it is perfectly legal in many situations to have object, listening for some events, and having no other references keeping it from GC. Logger can be an example.

As you can see, using WeakReference not just solve one problem ("I should keep in mind to not forget to remove added listener somewhere"), but also rise another -- "I should keep in mind that my listener can stop listen at any moment when there is no reference to it anymore". You not solve problem, you just trade one problem for another. Look, in any way you've forced to clearly define, design and trace livespan of you listener -- one way or another.

So, personally, I agree with mention what use WeakReference in listeners lists is more like a hack than a solution. It's pattern worth to know about, sometimes it can help you -- to make legacy code work well, for example. But it is not pattern of choice :)

P.S. Also it should be noted what WeakReference introduce additional level of indirection, which, in some cases with extremely high event rates, can reduce performance.

Solution 2 - Java

This is not a complete answer, but the very strength you cite can also be its principal weakness. Consider what would happen if action listeners were implemented weakly:

button.addActionListener(new ActionListener() {
    // blah
});

That action listener is going to get garbage collected at any moment! It's not uncommon that the only reference to an anonymous class is the event to which you are adding it.

Solution 3 - Java

I have seen tons of code where listeners were not unregistered properly. This means they were still called unnecessarily to perform unnecessary tasks.

If only one class is relying on a listener, then it is easy to clean, but what happens when 25 classes rely on it? It becomes much trickier to unregister them properly. The fact is, your code can start with one object referencing your listener and end up in a future version with 25 objects referencing that same listener.

Not using WeakReference is equivalent to taking a big risk of consuming unnecessary memory and CPU. It is more complicated, trickier and requires more work with hard references in the complex code.

WeakReferences are full of pros, because they are cleaned up automatically. The only con is that you must not forget to keep a hard reference elsewhere in your code. Typically, that would in objects relying on this listener.

I hate code creating anonymous class instances of listeners (as mentioned by Kirk Woll), because once registered, you can't unregister these listeners anymore. You don't have a reference to them. It is really bad coding IMHO.

You can also null a reference to a listener when you don't need it anymore. You don't need to worry about it anymore.

Solution 4 - Java

There are really no pros. A weakrefrence is usually used for "optional" data, such as a cache where you don't want to prevent garbage collection. You don't want your listener garbage collected, you want it to keep listening.

Update:

Ok, I think I might have figured out what you are getting at. If you are adding short-lived listeners to long-lived objects there may be benefit in using a weakReference. So for example, if you were adding PropertyChangeListeners to your domain objects to update the state of the GUI that is constantly being recreated, the domain objects are going to hold on to the GUIs, which could build up. Think of a big popup dialog that is constantly being recreated, with a listener reference back to an Employee object via a PropertyChangeListener. Correct me if I'm wrong, but I don't think the whole PropertyChangeListener pattern is very popular anymore.

On the other hand, if you are talking about listeners between GUI elements or having domain objects listening to GUI elements, you won't be buying anything, since when the GUI goes away, so will the listeners.

Here are a couple interesting reads:

http://www.javalobby.org/java/forums/t19468.html

https://stackoverflow.com/questions/1878127/how-to-resolve-swing-listener-memory-leaks

Solution 5 - Java

To be honest I don't really buy that idea and exactly what you expect to do with a addWeakListener. Maybe it is just me, but it appear to be a wrong good idea. At first it is seducing but the problems it might implies are not negligible.

With weakReference you are not sure that the listener will no longer be called when the listener itself is no longer referenced. The garbage collector can free up menmory a few ms later or never. This mean that it might continue to consume CPU and make strange this like throwing exception because the listener shall not be called.

An example with swing would be to try to do things you can only do if your UI component is actually attached to an active window. This could throw an exception, and affect the notifier making it to crash and preventing valid listeners to be notofied.

Second problem as already stated is anonymous listener, they could be freed too soon never notified at all or only a few times.

What you are trying to achieve is dangerous as you cannot control anymore when you stop receiving notifications. They may last for ever or stop too soon.

Solution 6 - Java

Because you are adding WeakReference listener, I'm assuming, you are using a custom Observable object.

It makes perfect sense to use a WeakReference to an object in the following situation.

  • There is a list of listeners in Observable object.
  • You already have a hard reference to the listeners somewhere else. (you'd have to be sure of this)
  • You don't want the garbage collector to stop clearing the listeners just because there is a reference to it in the Observable.
  • During garbage collection the listeners will be cleared up. In the method where you notify the listeners, you clear up the WeakReference objects from the notification list.

Solution 7 - Java

In my opinion it's a good idea in most cases. The code that is responsible for releasing the listener is at the same place where it gets registered.

In practice i see a lot of software which is keeping listeners forever. Often programmers are not even aware that they should unregister them.

It usually is possible to return a custom object with a reference to the listener that allows manipulation of when to unregister. For example:

listeners.on("change", new Runnable() {
  public void run() {
    System.out.println("hello!");
  }
}).keepFor(someInstance).keepFor(otherInstance);

this code would register the listener, return an object that encapsulates the listener and has a method, keepFor that adds the listener to a static weakHashMap with the instance parameter as the key. That would guarantee that the listener is registered at least as long as someInstance and otherInstance are not garbage collected.

There can be other methods like keepForever() or keepUntilCalled(5) or keepUntil(DateTime.now().plusSeconds(5)) or unregisterNow().

Default can be keep forever (until unregistered).

This could also be implemented without weak references but phantom references that trigger the removal of the listener.

edit: created a small lib which implements a basic version of this aproach https://github.com/creichlin/struwwel

Solution 8 - Java

I can't think of any legitimate use case for using WeakReferences for listeners, unless somehow your use case involves listeners that explicitly shouldn't exist after the next GC cycle (that use case, of course, would be VM/platform specific).

It's possible to envision a slightly more legitimate use case for SoftReferences, where the listeners are optional, but take up a lot of heap and should be the first to go when free heap size starts getting dicey. Some sort of optional caching or other type of assisting listener, I suppose, could be a candidate. Even then it seems like you'd want the internals of the listeners to utilize the SoftReferences, not the link between the listener and listenee.

Generally if you're using a persistent listener pattern, though, the listeners are non-optional, so asking this question may be a symptom that you need to reconsider your architecture.

Is this an academic question, or do you have a practical situation you're trying to address? If it's a practical situation I'd love to hear what it is -- and you could probably get more, less abstract advice on how to solve it.

Solution 9 - Java

I have 3 suggestions for the original poster. Sorry for resurrecting an old thread but I think my solutions were not previously discussed in this thread.

First, Consider following the example of javafx.beans.values.WeakChangeListener in the JavaFX libraries.

Second, I one upped the JavaFX pattern by modifying the addListener methods of my Observable. The new addListener() method now creates instances of the corresponding WeakXxxListener classes for me.

The "fire event" method was easily modified to dereference the XxxWeakListeners and to remove them when the WeakReference.get() returned null.

The remove method was now a bit nastier since I need to iterate the entire list, and that means I need to do synchronization.

Third, Prior to implementing this strategy I employed a different method which you may find useful. The (hard reference) listeners got a new event they did a reality check of whether or not they were still being used. If not, then they unsubscribed from the observer which allowed them to be GCed. For short lived Listeners subscribed to long lived Observables, detecting obsolescence was fairly easy.

In deference to the folks who stipulated that it was "good programming practice to always unsubscribe your listeners, whenever a Listener resorted to unsubscribing itself, I made sure to create a log entry and corrected the problem in my code later.

Solution 10 - Java

WeakListeners are useful in situations where you specifically want GC to control the lifetime of the listener.

As stated before, this really is different semantics, compared to the usual addListener/removeListener case, but it is valid in some scenarios.

For example, consider a very large tree, which is sparse - some levels of nodes are not explicitly defined, but can be inferred from parent nodes further up the hierarchy. The implicitly defined nodes listen to those parent nodes that are defined so they keep their implied/inherited value up to date. But, the tree is huge - we don't want implied nodes to be around forever - just as long as they are used by the calling code, plus perhaps a LRU cache of a few seconds to avoid churning the same values over and over.

Here, the weak listener makes it possible for child nodes to listen to parents while also having their lifetime decided by reachability/caching so the structure doesn't maintain all the implied nodes in memory.

Solution 11 - Java

You may also need to implement your listener with a WeakReference if you are unregistering it somewhere that isn't guaranteed to be called every time.

I seem to recall we had some problems with one of our custom PropertyChangeSupport listeners that was used inside row Views in our ListView. We couldn't find a nice and reliable way to unregister those listeners, so using a WeakReference listener seemed the cleanest solution.

Solution 12 - Java

It appears from a test program that anonymous ActionListeners will not prevent an object from being garbage collected:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;

public class ListenerGC {

private static ActionListener al = new ActionListener() {
		@Override
		public void actionPerformed(ActionEvent e) {
			System.err.println("blah blah");
		}
	};
public static void main(String[] args) throws InterruptedException {
		
	{
		NoisyButton sec = new NoisyButton("second");
		sec.addActionListener(al);
		new NoisyButton("first");
		//sec.removeActionListener(al);
		sec = null;
	}
	System.out.println("start collect");
	System.gc( );
	System.out.println("end collect");
	Thread.sleep(1000);
	System.out.println("end program");
}

private static class NoisyButton extends JButton {
	private static final long serialVersionUID = 1L;
	private final String name;

	public NoisyButton(String name) {
		super();
		this.name = name;
	}

	@Override
	protected void finalize() throws Throwable {
		System.out.println(name + " finalized");
		super.finalize();
	}
}
}

produces:

start collect
end collect
first finalized
second finalized
end program

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionpdevaView Question on Stackoverflow
Solution 1 - JavaBegemoTView Answer on Stackoverflow
Solution 2 - JavaKirk WollView Answer on Stackoverflow
Solution 3 - JavaJérôme VerstryngeView Answer on Stackoverflow
Solution 4 - JavaJames ScrivenView Answer on Stackoverflow
Solution 5 - JavaNicolas BousquetView Answer on Stackoverflow
Solution 6 - JavaDeepakView Answer on Stackoverflow
Solution 7 - JavaChristianView Answer on Stackoverflow
Solution 8 - JavajkraybillView Answer on Stackoverflow
Solution 9 - JavaTetoView Answer on Stackoverflow
Solution 10 - JavamdmaView Answer on Stackoverflow
Solution 11 - JavaDan JView Answer on Stackoverflow
Solution 12 - JavagerardwView Answer on Stackoverflow