Home

Follow Skinkers.

 

Blog.

4

Memory Management in AIR / AS3 / Flash Garbage Collection.

  October 8th, 2010 | Air, AS3, Flex, memory management | Matt Bryson

Memory management and garbage collection in Flash Player is a well debated topic. Some think that the Flash player’s garbage collections is, well, useless. Others think that most flash developers simply don’t understand it and their poor code causes memory leaks.

I’m not here to get into all that, its probably a bit of both anyway, but needless to say it will cause even the best Flash / Flex developer a headache or two at some point!

There are some good resources on Flash player garbage collection -How the GC works and How to kick start the GC in Air, so read up on those first if you haven’t already.

Below are our recent experiences with an Air application that did not release memory when a window was closed.

The Air application ran all the time in the background. When the user closes a window, we wanted all the memory used for that window to be released -but this wasn’t the case!

Looking at the memory profiler, everything from the Window object down was still in memory. I thought we had done the usual, free-up event listeners, null external references etc, but it still wasn’t working.

Below outlines what we did to free up memory, as well as how and when you execute this code.

What we did to successfully close an Air window and release its memory

  • Remove external references
  • Remove effect references
  • Remove any Tween listener references
  • Remove any function references (eg ArrayCollection Filter functions)
  • Remove any parent references to a component with deleteReferenceOnParentDocument
  • Remove all event handlers
  • Remove any manually created bindings
  • Dispose of any bitmap data
  • Dispose of any XML data
  • Kick start the garbage collector


Remove external references
If the root of the Flash player has a reference to an object, that object wont be garbage collected. Likewise, if a chain of objects can be linked back to the root, they wont be collected. So when trying to destroy your object, ensure that all references to external objects are nulled

//if you have an owner property and the owner its not going to be collected, you will need to clear that reference.
this.owner = null;



Remove effect references
If you use the mx.Effects classes, such as Fade (either in MXML or AS) these contain a reference to the objects they apply the effects too. This reference will prevent the garabge collector from destroying your object, so clear the target property of the effect when you want to destroy your object.

In the MXML….

<mx:Fade alphaTo="1" id="fadeIn" />
<mx:Fade alphaTo="0" id="fadeOut" />

<!-- later in a view stack.... -->
<local:MyComponent showEffect="fadeIn" hideEffect="fadeOut"/>

In your clean up method…

//Clear the effect referecne
fadeOut.target=null;
fadeIn.target=null;



Remove any Tween listener references
If you use the AS class Tween, you have to assign a listener object to handle it’s update events. This listener must be cleared if you want to free up the listener object.

//End the tween and remove the listener reference.
tween.endTween();
tween.listener=null;



Remove any function references (eg ArrayCollection Filter functions)
If your object has any external references to its methods you need to clear those as well. For example take a component that takes a data provider and will filter the data provider. It may assign one of its methods as the Filter Function for that data provider – which now means you have an external reference (via the dataProvider.filterFunction) to your object. This needs to be cleared when you want to destroy your object.

When the DP is set, a filter function is assigned.

public function set dataProvider(value:ArrayCollection):void
{
	this._dataProvider=value;
	this._dataProvider.filterFunction = this.defaultFilterFunction;
}

When you destory this component you need to clear the reference…

this.dataProvider.filterFunction=null;
this.dataProvider = null;

Or, when setting the dp to null, ensure you free the function reference…

public function set dataProvider(value:ArrayCollection):void
{
	if (value==null && this._dataProvider)
		this._dataProvider.filterFunction=null;

	this._dataProvider=value;

	if (this._dataProvider)
		this._dataProvider.filterFunction = this.defaultFilterFunction;
}



Remove any parent references to a component with deleteReferenceOnParentDocument
UIComponents have a handy method called deleteReferenceOnParentDocument that will remove any references in the parent document to your object. Calling this from your component when trying to destroy it can help free up references.

//Remove parent references
_textInput.deleteReferenceOnParentDocument(this);



Remove all event handlers
If an event handler is still registered, flash player wont garbage collect the event handler object as it still has references to it. So always remove all event handlers when ready to destroy your object. Some people say to always use week references, however, this could lead to unforeseen behaviour if you handler is garbage collected and you don’t wan’t it to be. Also, we found adding week references didn’t actually help!

//Remove listeners to internal components
_dropdown.removeEventListener(ListEvent.ITEM_CLICK, dropdown_itemClickHandler);
_dropdown.removeEventListener(ResizeEvent.RESIZE, dropdown_resizeHandler);				

//Remove listeners to external objects
systemManager.removeEventListener(Event.RESIZE, stage_resizeHandler);



Remove any manually created bindings
If you create a binding using the BindingUtils then you must remove that binding, else you have references to your object. Flex will take care of any auto bindings created with the [Bindable] meta data.


private var watcher:ChangeWatcher;

//At creation you may do....
watcher = BindingUtils.bindSetter( updateX, myObject, "x");

//Then when we want to destory the object
watcher.unWatch();
watcher=null;



Dispose of any bitmap data
If your object uses bitmap data, and that data is no longer needed, you can manually free up the memory that the bitmap data uses with the dispose method.

//kill our image bitmap data...
Bitmap(myImage.source).bitmapData.dispose();



Dispose of any XMLdata
Due to the parent child relationship within XML documents, they are very hard to destroy. However, there is a utility you can use to force the XML doc to give up its memory allocation – with the System.disposeXML method. This will remove the XML, so only run this if you are sure you have finished with the XML doc and no longer want to reference it.

//kill our XML data...
System.disposeXML( myXMLObject);



Kick start the garbage collector
Once all this was done, we then kicked the garbage collector into action using the techniques described in this post.



How and when do you run the clean up code

This is all very good, however, WHEN and HOW do you run these actions to free up memory?

Well, you could use event handlers – say a REMOVED_FROM_STAGE event to run you clean up code, but you might want to re add the item later.

The approach we take is to have an Interface IDisposable that enforces a dispose method. It’s in this method that you would define all your clean up code.

Then you register the IDisposbale object with a register of all the objects you want to dispose – a static class aptly named DisposableRegister.

This has 4 public static methods. register to add a object to the register, remove to remove an object from the register, hasObject to check if an object is registered, rand finally the important disposeObjects that will dispose of all the registered objects.

A basic example of the disposabel register.

public class DisposableRegister
	{
		private static var disposables:Array=new Array();

		/**
		 * Regsiters an IDisposable object with the disposable register.
		 * @param disposableObject Any IDisposable object.
		 */
		public static function register(disposableObject:IDisposable):void
		{
			if (disposables.indexOf(disposableObject)==-1)
				disposables.push(disposableObject);
		}

		/**
		 * Removes an IDisposable object from the disposable register.
		 * @param disposableObject Any IDisposable object.
		 */
		public static function remove(disposableObject:IDisposable):void
		{
			for (var i:int=0; i<disposables.length; i++)
			{
				if(disposables[i]==disposableObject)
				{
					disposables.splice(i,1);
					return;
				}
			}
		}

		/**
		 * Calls the dispose() method on all the registered IDisposable objects.
		 */
		public static function disposeObjects():void
		{
			for (var i:int=0; i<disposables.length; i++)
			{
				(disposables[i] as IDisposable).dispose();
				disposables[i]=null;
			}

			disposables=new Array();
		}

		/**
		 * Checks if the object passed has been registered with the DisposableRegister.
		 * @param disposableObject Any IDisposable object.
		 */
		public static function hasObject(object:Object):Boolean
		{
			for (var i:int=0; i<disposables.length; i++)
			{
				if (disposables[i] == object)
					return true;
			}

			return false;
		}
	}

You could take the example further by adding in the notion of a “key” to register the object against. This way you could have multiple registers, say for levels in a game.

//In your constructors for level one related objects add.
DisposabelRegister.register(this, "level1");

//Then when you want to unload level one,
DisposabelRegister.disposeObjects("level1");

An example of a IDisposable object would be….

package
{
	import com.skinkers.utils.gc.IDisposable;
	import com.skinkers.utils.gc.DisposableRegister;
	import mx.collections.ArrayCollection;
	import mx.collections.ICollectionView;
	import flash.events.Event;

	public class MyObject extends Object implements IDisposable
	{

		public function MyObject()
		{
			//Register this instance with the disposable register so we can be destroyed later.
			DisposableRegister.register(this);	

			systemManager.addEventListener(Event.RESIZE, stage_resizeHandler);
		}

		/**
		 *  Storage for the dataProvider property.
		 */
		private var _dataProvider:ICollectionView = new ArrayCollection();

		/**
		 * The dataprovider that populates the drop down
		 */
		public function set dataProvider(value:ICollectionView):void
		{
			_dataProvider = value;

			if (value)
				_dataProvider.filterFunction = defaultFilterFunction;

		}

		public function get dataProvider():ICollectionView
		{
			return _dataProvider;
		}

		/**
		 *  The default filter function used when filtering the Dataprovider
		 */
		private function defaultFilterFunction(element:*, text:String):Boolean
		{
			var label:String = itemToLabel(element);
			return (label.toLowerCase().substring(0,text.length) == text.toLowerCase());
		}

		/**
		* Clean up and remove all references
		* so we can be garbage collected.
		*/
		public function dispose():void
		{
			_dataProvider.filterFunction=null;
			_dataProvider = null;

			systemManager.removeEventListener(Event.RESIZE, stage_resizeHandler);
		}

	}

Then, when you want to free up the objects, for example when a window closes, you can simply call

		DisposableRegister.dispose();

Using the IDisposable forces you to think about how the object is constructed and destroyed, and the DisposableRegister is just a simple way of triggering all your dispose methods.

Adding dispose methods to all our objects, and cleaning up in the manner described above finally freed up the window memory when the window was closed.

It doesn’t take much to add a dispose method and can save you hours later profiling the application trying to find out why memory is not being freed up!

m.

4 Responses to “Memory Management in AIR / AS3 / Flash Garbage Collection.”

  1. Mik says:

    Ahhh !

    You saved me. thank you for making the *PROPER* guide !

    I just finished to work on a major part in my project that duplicated a lot of graphic videos in sub-windows,
    and after a few duplication and windows “deletion” the application took over 500MB of memory !

    Now I have to investigate all my code, refactor, and hope for the best.

    Thanks =)

    Mik, Israel

  2. Hani Delphys says:

    thanks, very helpful

  3. Mark Lapasa says:

    What evidence do you have in your experience where weakReferences didn’t actually help? I am interested in the setup of this scenario where the function object reference doesn’t get picked up by the GC when the class that owns the function object gets GC’d.

Leave a Reply

*