Ginger Binger

Lessons in programming, learning, and life

AS3 Garbage Collection With Closures

| Comments

I was talking with Ido about memory usage in AS3, and we discovered a small weakness in the AVM’s garbage collector. If you use a functional style of programming with lots of anonymous or nested functions, here’s something to watch out for:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import flash.display.Sprite;
import flash.events.Event;

function foo():* {
    var sprite:Sprite = new Sprite();
    sprite.addEventListener(Event.ENTER_FRAME, tick);

    // this closure creates a reference to the outer scope, foo
    return function():void { }
}

function tick(event:Event):void {
    trace("tick");
}

var func:* = foo();
System.gc();
// sprite is never garbage collected, and continues to tick

sprite will not be garbage collected, even though it’s just a local variable of foo and no other references to it exist!

The issue is the anonymous function returned by foo. This closure creates a reference to the outer scope, foo. This is necessary so that you can access the outer variables from inside the nested function. For example, the inner function might have done sprite.x = 10.

Unfortunately, outer variables are still referenced and stay alive, even if you never use them inside the closure! In our case, the free variable sprite is still referenced by the closure even though it’s never used in the anonymous function. It remains ineligible for garbage collection. Notice that if we change the return to return 0, then sprite is collected and stops ticking.

Here are some tips to avoid this issue:

  • Ensure that closures will be garbage collected by clearing references to them. If we eventually do func = null, then our closure is no longer reachable and will be collected, along with sprite.

  • Avoid using nested functions and anonymous functions.¬†Instead of using an anonymous inner function, we could move the function outside:

1
2
3
4
5
6
function foo():*
{
    return myFunc;
}

function myFunc():* { }
  • Null out any local variables at the end of the outer function. We can set sprite = null at the end of the function since we won’t be using it any longer.

This isn’t a big issue, but it’s something to keep in mind if you’re juggling function references with chunky objects like BitmapData. Is it possible for a VM to be smart enough to reference only the objects that are actually needed, instead of pushing the entire outer scope? I’m not sure. I wonder if other VMs such as V8 handle this any better?

Comments