2008-08-30

AIR Garbage Collection and Memory Leaks

Summary: Following insights came to me when debugging a new AIR application that was built to be used as a printing service, polling a database for printjobs, setting a timeout and then repeatedly poll again. Using Flex Builder Profiler showed first an obvious memory leak as several of the project classes increased in number of instances over time. This resulted in a crash of the Air Debug Launcher (adl.exe). However, after fixing the obvious by best practise as described below, and verifying that the Profilers memory profile was kept constant, I found that the total memory usage given by the Profilers analysis was NOT the same as the trace of flash.system.System.totalMemory!

Best practise in programming AS3, whether you are hacking *.as classes or *.mxml, should always use a destuctor function. Let us use the same convention as for the good example of the BitmapData class, and call it dispose():void. That will also make good sens to you .Net hackers for the same reason. The BitmapData.dispose() function frees memory for garbage collection.

You will have to make sure the dispose functions are called when instantiated objects no longer are in used and need to be referenced. Creating a new Array() of objects, running ArrayCollection.removeAll(), or setting each object to null does not guarantee garbage collection. References within the objects still may be held by listeners and/or other classes.

For *.mxml you will most likely use protected functions as onCreationComplete():void and onAddedToStage():void to do some initialization, bindings, and listening - just as with the class constuctor. Just make sure you also declare a public function dispose():void that eather can be called internally by a remove event, or externally by any controller or command. The function may look something like this:
public function dispose():void {
bitmapData.dispose();
swfLoader.loaderInfo.loader.unload();
removeListener("type", callbackfunction);
CairngormEventdispatcher.getInstance().removeListener(
"type", callbackfunction);
file.close();
... and so forth
}

Being consequent and thorough will also give you the possibillity to force garbage collection once in a while between two frames by doing the following:

private var gcCount:int;
private function startGCCycle():void{
gcCount = 0;
addEventListener(Event.ENTER_FRAME, doGC);
}

private function doGC(evt:Event):void{
flash.system.System.gc();
if(++gcCount > 1){
removeEventListener(Event.ENTER_FRAME, doGC);
clearTimeout(timeOut);
timeOut = setTimeout(lastGC, 50);
}
}

private function lastGC():void{
clearTimeout(timeOut);
flash.system.System.gc();
}

Thank you Grant skinner. Read more:
www.flexdeveloper.eu
blogs.warwick.ac.uk
www.gskinner.com

No comments: