Memory management is tricky when working with a garbage collector. Throw in a complex display list hierarchy with lots of event handlers, and it’s definitely a challenge! It’s very easy to overlook some hidden object reference, causing objects to not be marked as garbage. Soon your memory usage is spiraling out of control! This has lead many developers such as Grant Skinner and Ted Patrick to suggest always setting useWeakReference to true when you call addEventListener.
This is indeed a good idea and can be a helpful failsafe, but I think that it fails to emphasize another important action: Always remove your event listeners. I fear that many developers may be given a false sense of security by always using weakly referenced listeners, so I’ve tried to create a clear description of how event listeners create references and how to clean them up. This assumes you have an idea of how Flash’s mark-sweep garbage collector works. If not, check out Grant’s excellent AS3 Resource Management articles, which are a must read for any Flash developer.
Event listeners and references
Let’s look at the creation of a simple event listener:
dispatcher.addEventListener(Event.COMPLETE, listener.handlerMethod);
Here we can see the two objects involved in event handling: the dispatcher and the listener. Note that they could refer to the same object. They could also be omitted, in which case they implicitly refer to “this” (i.e., the object running that line of code).
addEventListener creates a reference from the dispatcher to the listener. The reference is needed to call the handler function when the event fires. This means that listener can not be garbage collected until it is removed from the dispatcher, or until dispatcher is also eligible for garbage collection.
(a) The root holds a reference to an event dispatcher. Because the listener is still reachable in the object graph, it can not be garbage collected until (b) the listener is removed using removeEventListener, or (c) the references to the dispatcher itself are cleared, allowing for both items to be collected.
Listeners do not prevent a dispatcher from being garbage collected. References are one way, not bidirectional. If we cleared all references to dispatcher, it would have no references pointing to it. It would be eligible for garbage collection, regardless of whether we removed the listener.
(a) The root contains an object that is listening to a dispatcher. Even though someone is still listening, (b) the dispatcher is unreachable and is eligible for garbage collection, assuming no other references to the dispatcher exist.
useWeakReference
Let’s look at how useWeakReference affects things. Weak references are not traversed during mark-sweep garbage collection. Therefore, weak references will allow your listener objects to be garbage collected in certain situations where they otherwise would not. Let’s consider the common example of a Player class that would watch for key presses:
public class Player extends MovieClip {
public function initPlayer():void {
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}
public function onKeyDown(e:Event):void { /* ... */ }
}
Imagine that our player dies, and we want him to be cleaned up. However, the event listener creates a reference from the stage to the player. The stage is the topmost display object and is always accessible. Therefore, when the mark-sweep process runs, this event listener allows the garbage collector to hop from the stage to our player object, even if we’ve cleared all other references and removed it from the display list. Our player object will never be collected and will sit around wasting memory! But what if we set useWeakReference to true?
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown, false, 0, true);
Now, despite our listener, the garbage collector can not travel from the stage to our player object. The player will be marked as unreachable and eligible for collection. Weak references helped us out here.
(a) The player is visible on the stage, listening for keyboard presses. If the player dies and we remove him from the stage, (b) the event listener will prevent his garbage collection. (c) Using a weakly referenced listener will allow the player to be garbage collected, even if we forget to remove the listener.
However, let’s consider a different example. Imagine a shooting game where an enemy is killed when clicked by the user:
public class Game extends MovieClip {
private var enemy:Enemy;
public function Game() {
enemy = new Enemy();
addChild(enemy);
enemy.addEventListener(MouseEvent.MOUSE_DOWN, onEnemyClicked, false, 0, true);
}
public function onEnemyClicked(event:Event):void
{
removeChild(enemy); // kill the enemy
enemy = null;
}
}
In this example, the enemy is the dispatcher, creating a reference from the enemy to the root game object. It doesn’t matter if we use a weak reference here. After our enemy dies, when the garbage collector runs, it can’t hop from the root to the enemy. The reference is in the opposite direction, from enemy to root! Therefore, the enemy doesn’t get marked and will be eligible for garbage collection, even though someone is still listening to it.
(a) The root has many references to an enemy, and is listening for an event from the enemy. When the enemy dies and is removed from the display list, (b) the enemy is unreachable and is eligible for garbage collection, even if we don't use a weak reference or remove the listener.
useWeakReference only has an effect when the listener object has a shorter lifetime than the dispatcher. Usually this happens when a “child” object is listening for events from a “parent” object. In our first example, the player was a transient child object listening to the long-lived global stage. This is the minority case—more often, an object will listen to events from child objects or itself.
Unfortunately, all of this talk misses an even bigger problem. Regardless of any use of weak references, an object may continue to dispatch and listen to events until it gets garbage collected. Since the garbage collector runs at some indeterminate point in the future (possibly never), this could take quite a while! In our first case, the stage will continue to throw KEY_DOWN events, triggering a response inside the player, even though he’s dead! For events like KEY_DOWN and ENTER_FRAME, this is a source for many subtle bugs and performance problems. We really need to remove our listeners manually!
Clean up after yourself
All of the above pitfalls are avoided if we follow a simple rule: Regardless of whether or not you use weak references, at the end of an object’s lifetime, always remove any listeners that it created. If you clear your listeners when you’re done with them, then you will both prevent your events from firing when an object is “dead”, and you will ensure that listeners are not preventing garbage collection of your objects!
Here’s a good checklist to follow when you are done with an object:
- Remove it from the display list.
- If it’s a MovieClip, tell it to stop().
- Remove any event listeners that the object has created.
- Clear any references in parent objects by setting them to null.
Implementing a dispose method on your objects makes it easy to manage your cleanup duties. Here’s some sample code illustrating these concepts:
package
{
import flash.display.MovieClip;
public class Game extends MovieClip
{
public var player:Player;
public function Game()
{
player = new Player(); // creates a reference from Game to Player
addChild(player); // creates both a reference from Game to Player and vice versa
player.initPlayer(); // more references created here (see below)
}
// all player die someday!
public function killPlayer():void
{
removeChild(player); // clear the two references created by the display list
player.dispose(); // tell player to clear its own references and event listeners
player = null; // finally, get rid of the direct variable reference
}
}
}
package
{
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.KeyboardEvent;
public class Player extends MovieClip
{
public function initPlayer():void {
// creates a reference from the stage to player
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
// creates a self-reference from player to player
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onKeyDown(event:Event):void { /* ... */ }
private function onEnterFrame(event:Event):void { /* ... */ }
public function dispose():void
{
// clean up after ourself!
stop();
stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
removeEventListener(Event.ENTER_FRAME, onEnterFrame);
}
}
}
{ 6 comments… read them below or add one }
Awesome
Very nice explanation. I try to always remove listeners regardless of where they were added, and only use weak reference as a safety for adding listeners to the stage like the examples you gave.
The only thing I don’t understand is why addEventListener doesn’t use weak reference by default, rather than the other way around as it is. I can’t think of a single scenario where if there were no references to an object besides a dispatcher’s listener reference that I would not want the listener object to be garbage collected, or that I would want the listener to function indefinitely. Is there one?
There are a few cases where using a weak reference can actually cause issues. For example, here’s a pathological case:
addEventListener(event, function(e:Event):void { trace(“Hello”); });
The anonymous function has no references to it, so using a weak reference will make it eligible for garbage collection immediately.
Sure, that’s a dumb case, but most of the problems boil down to the same issue. I once saw someone get in trouble with an object that added a weakly referenced SOUND_COMPLETE listener to a SoundChannel. Unfortunately, the object itself didn’t have any other references, so it was getting garbage collected, and the handler never got called.
So I guess maybe the Flash team thought it was safer to default it to false, along with the fact that weak references are rare in the AS3 language. Of course, problems caused by not removing strongly referenced listeners are probably more common, so who knows?
Good points, thanks for the follow up, Mike.
Thanks a lot for the great article!
Great post!!! Thank you very much!
{ 3 trackbacks }