AS3 memory leak prevention

Ran into a really nice memory leak the other day, it caused me to lose about a day’s work correcting my code to handle clean-up in a better way. The FP11 Garbage Collection routines will handle most of the work for you, but a few changes to code can go a long way.

Understanding the FP11 GC model’s inner workings is quite valuable, I have posted a link to a great article on the subject. Here are some of the practices I use to avoid these kinds of problems.

 
1. Destroy your class

AS3 programmers seem to rarley use destructors in their classes, different data types give you different options to hook into a “destructor” event. Any display object that gets added to your stage will allow you to track AddedToStage and RemovedFromStage events, thus giving you access to hooks to clean up your class. Here is an example:

package {

	import flash.display.Sprite;
	import flash.events.Event;

	public class MyClass extends Sprite {

		private var _box:Sprite;

		function MyClass():void {
			_box = new Sprite();
			_box.graphics.beginFill(0x000000);
			_box.graphics.drawRect(20,20,200,200);
			_box.graphics.endFill();

			addEventListener(Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true);
			addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true);

			addChild(_box);
		}

		private function onAddedToStage(evt:Event):void {
			trace("I have just been added!");
			// do something...
			removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		}

		private function onRemovedFromStage(evt:Event):void {
			trace("I'm cleaning up now!");
			trace("My property before cleanup: "+_box);
			trace("My number of children before cleanup: "+this.numChildren);
			// clean-up
			removeChild(_box);
			_box = null;

			trace("My property after cleanup: "+_box);
			trace("My number of children after cleanup: "+this.numChildren);
			removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
		}

	}

}

The instancing code:

import MyClass;

var myobject:MyClass = new MyClass();
this.addChild(myobject);
this.removeChild(myobject);

This class effectivly cleans up after itself, it removes it’s assigned listeners once they have completed and removes all of it’s child display objects. Then by assigning the _box property to null we are telling the garbage collection that it is ok to collect this before destroying the class.

Here is another example that is not a DisplayObject.

package {

	public class MyClass {

	private var _data:Object;

		function MyClass() {
			_data = new Object();
			_data.myData = "a test string";
		}

		public function destroy() {
			trace("cleaning up...");
			trace("My property _data.myData before cleanup:"+_data.myData);
			delete(_data.myData);  // this will work because myData is a dynamically created property!
			trace("My property _data.myData after cleanup:"+_data.myData);
			_data = null;
			trace("My property _data after cleanup:"+_data);
		}

	}

}

Instancing code:

import MyClass;

var myobject:MyClass = new MyClass();
// do some stuff
// now i'm done. clean up
myobject.destroy();

This class depends on you to call the destroy method but the extra work is worth it because the class won’t get missed by the FP11 GC when the time comes.

2. It’s good to be weak

Events and event listeners are intrinsic to powerful as3 applications, you can still write a decent application with minimal events but there are LOTS of problems that can be solved easily by using them. One of the most common memory leaks is not properly removing event listeners or not using weak references in your listeners.

for example, take this event listener:

addEventListener(MouseEvent.CLICK, onClickHandler);

though this will work correctly, unless you explicitly call removeEventListener() then the listener will always be attached to the class that it exists in. and worse yet may not get flagged for cleanup by the GC once it is not used anymore. a quick fix is to use “weak references”, basically it tells the FP11 GC that once no reference is found for this listener then ALWAYS clean it up. This is a great safety in case you forget to call removeEventListener. Custom events will benefit greatly from using weak references.

Same event but with a weak reference:

addEventListener(MouseEvent.CLICK, onClickHandler, false, 0, true);

The extra 3 arguments are “bubbles”, “priority” and “use weak reference”, by telling the event not to bubble up the call chain, have a low priority and use a weak reference we save ourselves from forgetting to clean up. Still a removeEventListener() call should be made just for good practice’s sake. ;)

3. Even locals have die

Good cleanup practices are essential in your objects, but often cleanup in your object’s methods can be just as crucial. These types of problems don’t cause memory leaks that often (but they can!), but they do contribute to performance bottlenecks. Let’s take this method for example.


private function doSomething():Array {
     var output:Array = new Array()

     for(var i:int=0; i < 30; ++i) {
          var obj:Object = new Object();
          obj.id = i;

          // add to array
          output.push(obj);
     }

     return output;
}

This method doesn't do anything important but the question is how can we save memory while it runs? One idea is to kill the obj variable after reach loop, thus ensuring that we aren't adding anything useless to the stack. Here is the new example.


private function doSomething():Array {
     var output:Array = new Array()

     for(var i:int=0; i < 30; ++i) {
          var obj:Object = new Object();
          obj.id = i;

          // add to array
          output.push(obj);

          // clean up
          delete(obj.id);
          obj = null;
     }

     return output;
}

This example always cleans up it's local variables. This optimization is not always necessarily but is good practice.

So these are a few of the ways that I use to ensure that I avoid memory leaks in my applications. Have fun, happy, memory leak free coding!