Programming skills include C, C++, Java, ActionScript, PHP, MySQL. One of the most common concerns to any ActionScript3 developer is to make their applications memory efficient, by using memory efficient resources, writing optimized code and also always cleaning up unused objects after their roles have been fulfilled. Naturally, Flash garbage collector (GC) will make sure allocated memory chunks to objects that are no longer in use will be recollected, however it takes some skill to write proper code that facilitate the GC to do its job. An excellent account on garbage collection, indispensable for any AS3 learner, is an article by Gran Skinner. Based on his article, I will write example code that exhibit the GC's behaviors and mechanisms, thereby helping novice programmers fully understand GC in practice and be able to write better code.
Below is the example flash file that'll be used
Source code can be downloaded in the attachments. Please go ahead, download the source code and open GCExample.fla. As you can see, I create different buttons ( and placed them in frame 1) to test different scenarios, some scenarios can be run inside Flash IDE, others should be run on browsers, but we'll get to that difference shortly.
So I assume that you all have with you GCExample.fla open in Flash IDE. It has 6 frames. In frame 1 you find 5 buttons, each will be responsible for taking you to frame 2 to 6, which in turns will test a certain scenario.
The very first scenario on frame 2 is to test the property of a strong keyed dictionary. If you are wondering why class Dictionary, the reason is according to ActionScript API document of class Dictionary, the default constructor new Dictionary() creates a dictionary whose object keys are strong referenced. In other words, these keys are not eligible for gabarge collection. On the other hand, using new Dictionary(true) instructs the Dictionary object to use weak references on object keys, thereby enabling these keys to be garbage collectable. Thanks to this property of the Dictionary class, we now have a very interesting capability: object counting. To achieve this we create a new Dictionary object which use weak object keys. We then use the object of interest as a new key and assign the value at that key to anything, maybe a primitive, for example:
[as]
var aDict:Dictionary = new Dictionary(true);
var aObject:Object = new Object();
aDict[aObject] = 1;
[/as]
aObject is the object we want to keep track. To know if it has been garbage collected or not, we simply count the number of keys in aDict object, provided that we don't add any more key to aDict.
[as]
var n:int = 0;
for (var key:* in aDict) {
n++;
}
[/as]
If n equals 0, aObject has been freed, otherwise n equals 1.
With that in mind, we shall now find out if Dictionary really behaves as documented. Frame 2 code is as follows:
[as]
var myArray:Array = new Array();
var strongDict:Dictionary = new Dictionary();
addEventListener(Event.ENTER_FRAME, enterFrameStrongDic);
function enterFrameStrongDic(aEvent:Event):void
{
var anArray:Array = new Array(10000);
for (var i:int = 0; i < anArray.length; i++) {
anArray[i] = 0;
}
strongDict[anArray] = 1;
var n:int = 0;
for (var key:* in strongDict) {
n++;
}
trace("Number of keys " + n);
}
[/as]
First, a Dictionary object named strongDict, which use strong reference on object keys, is created. Then in every frame onwards, an array of 10,000 integers is created and added as an object key to strongDict. The number of keys in strongDict is counted and printed out every frame as well.
Run GCExample.fla and click on the top button labeled "Test Dictionary Strong" to see the result of the code. You'll see a text box at the bottom, which displays total memory Flash player is using. Without a doubt, the amount of consumed memory steadily increase, so does the number of keys. This proves that those arrays we created in function enterFrameStrongDic are not freed. If we hadn't put them in the Dictionary, they would have been freed, since they are local to enterFrameStrongDic and normally will be freed when reaching the end of that function.
Rerunning GCExample.fla and clicking on the second button from top, labeled "Test Dictionary Weak", will put the code in frame 3 to test.
[as]
var weakDict:Dictionary = new Dictionary(true);
addEventListener(Event.ENTER_FRAME, enterFrameWeakDic);
function enterFrameWeakDic(aEvent:Event):void
{
var anArray:Array = new Array(10000);
for (var i:int = 0; i < anArray.length; i++) {
anArray[i] = 0;
}
weakDict[anArray] = 1;
var n:int = 0;
for (var key:* in weakDict) {
n++;
}
trace("Number of keys " + n);
}
[/as]
This chunk of code is almost identical to the one in frame 2, except that weakDict, a Dictionary object which uses weak references on object keys, is utilized instead. This time the amount of consumed memory stays the same after an initial increase, also the number of keys remains 1. This outcome is exactly what we expected: GC constantly freed up the arrays that we constantly created.
Since we're now understood and can confidently use the Dictionary class, let's move on to another topic that a lot of people are concerned with: event listeners.
There are 2 contexts where weak reference is used. The first is Dictionary with weak references on object keys, and the second is adding event listeners using weak references. The reason you must be careful when using event listeners is that the EventDispatcher class doesn't have a method to just remove all event listeners that it has added before, hence you have to manually remove each event listener you've added. Imagine you use strong reference on event listeners and later forget to remove them from the event dispatcher, the event listeners will continue to exist as long as the event dispatcher exist, possibly leading to memory leak. Having said that, it's a good practice to always remove important event listeners manually, especially if the fact that they receive events or not will affect the logic flow of your program.
By default, addEventListener function add strong referenced event listener. To use weak reference, simply pass the parameter useWeakReference as true like this:
[as]
addEventListener(someEvent, eventHandler, false, 0, true);
[/as]
The advantage of using weak reference on event listeners is the event listeners and the objects that they are members of are now subject to garbage collection, thereby preventing memory leak. Note that class-level member functions are not subject to garbage collection.
The disadvantage is your code will be more verbose. Some developers go as far as recommending to always pass useWeakReference. I don't feel this necessary, for example, handlers function of UI components such as buttons or text fields will most likely reside within the same class that linked to the display container of those UI components, they are self contained in one class so there is no risk of memory leak, Flash GC will be able to clean them using mark sweeping strategy. (If you don't know what mark sweeping is, please refer to Grant's article that I mentioned at the beginning)
Now let's have a look at the setup for testing event listeners. First, there are 3 classes whose functions will be used as event listeners. ClassA extends MovieClip and it links to a symbol named ClassA in the .fla file.
[as]
public class ClassA extends MovieClip
{
public function ClassA()
{
}
public function customHandler(e:Event):void
{
trace("Class A custom Handler " + getTimer());
}
}
[/as]
ClassB and ClassC are the same.
[as]
public class ClassB
{
public function ClassB()
{
}
public function customHandler(e:Event):void
{
trace("Class B custom Handler " + getTimer());
}
}
[/as]
[as]
public class ClassC
{
public function ClassC()
{
}
public function customHandler(e:Event):void
{
trace("Class C custom Handler " + getTimer());
}
}
[/as]
Why we are testing a movie clip will be cleared shortly.
Code in frame 4:
[as]
this['mDisposableObjectA'] = new ClassA();
this['mDisposableObjectB'] = new ClassB();
this['mDisposableObjectC'] = new ClassC();
addEventListener(Event.ENTER_FRAME,
this['mDisposableObjectA'].customHandler,
false,
0,
true);
addEventListener(Event.ENTER_FRAME,
this['mDisposableObjectB'].customHandler);
addEventListener(Event.ENTER_FRAME,
this['mDisposableObjectC'].customHandler,
false,
0,
true);
btnRemove.addEventListener(MouseEvent.CLICK, removeClickHandler);
function removeClickHandler(e:Event):void
{
this['mDisposableObjectA'] = null;
this['mDisposableObjectB'] = null;
this['mDisposableObjectC'] = null;
}
[/as]
As can be seen, 3 instances of ClassA, ClassB and ClassC are created. Then their functions customHandler are added as event listener of Event.ENTER_FRAME of the stage. From now on, they will print out a message every frame:
[as]
...
Class B custom Handler 4644
Class C custom Handler 4644
Class A custom Handler 4732
Class B custom Handler 4732
Class C custom Handler 4732
Class A custom Handler 4820
Class B custom Handler 4820
Class C custom Handler 4820
Class A custom Handler 4908
Class B custom Handler 4908
Class C custom Handler 4908
...
[/as]
If we see the trace output of an instance, naturally it means that the instance still persists in memory, otherwise it has been garbage collected. It is designed that if you click on the button "Remove Reference", it will attempt to remove all 3 objects by setting them to null. Note that ClassA and ClassC instances were added as weak references but ClassB instance were added as strong reference. So it is expected that only ClassA and ClassC instances will be garbage collected. Run the .fla file, click on "Test Event Listener", then click "Remove Reference" and see for yourself. Well, a little unexpected, or as expected, outcome, something like this:
[as]
...
Class B custom Handler 341377
Class A custom Handler 341459
Class B custom Handler 341459
Class A custom Handler 341621
Class B custom Handler 341621
Class A custom Handler 341709
Class B custom Handler 341709
Class A custom Handler 341880
Class B custom Handler 341880
Class A custom Handler 341965
Class B custom Handler 341965
Class A custom Handler 342130
Class B custom Handler 342130
Class A custom Handler 342219
Class B custom Handler 342219
...
[/as]
So even though that ClassC instance has been garbage collected, ClassA instance persists. The reason is it's up to the garbage collector to decide when to collect unused objects. In this case, collecting objects such as MovieClip takes mark sweeping strategy since movie clips normally contains children, which in turn hold reference to their parent, in other words, circular reference. Mark sweeping is expensive, therefore, it doesn't get called casually but when Flash uses too much memory and it decides it's time to clean up. In fact, you can try leaving our test running for a very long time and ClassA instance still persists.
To prove that Flash doesn't want to remove ClassA instance and not that it cannot, let's move on to the next test on Frame 5.
This frame is basically the same as Frame 4, however, an array of 10,000 integers is constantly added to memory. This is kind of an incentive to encourage GC to recycle unused memory space. Run it by first choose "Test Event Listener Final" then "Remove Reference". The output:
[as]
....
Class A custom Handler 9935
Class B custom Handler 9935
Class C custom Handler 9935
Class A custom Handler 10027
Class B custom Handler 10027
Class C custom Handler 10027
Class A custom Handler 10111
Class B custom Handler 10111
Class C custom Handler 10111
Class A custom Handler 10194
Class B custom Handler 10194
Class C custom Handler 10194
Class A custom Handler 10281
Class B custom Handler 10281
Class A custom Handler 10367
Class B custom Handler 10367
Class A custom Handler 10455
Class B custom Handler 10455
Class A custom Handler 10543
Class B custom Handler 10544
Class A custom Handler 10631
Class B custom Handler 10632
Class A custom Handler 10719
Class B custom Handler 10719
Class A custom Handler 10811
Class B custom Handler 10811
Class A custom Handler 10895
Class B custom Handler 10895
Class A custom Handler 10983
Class B custom Handler 10983
Class A custom Handler 11071
Class B custom Handler 11071
Class A custom Handler 11159
Class B custom Handler 11159
Class A custom Handler 11247
Class B custom Handler 11247
Class A custom Handler 11336
Class B custom Handler 11336
Class A custom Handler 11423
Class B custom Handler 11423
Class A custom Handler 11511
Class B custom Handler 11511
Class A custom Handler 11599
Class B custom Handler 11599
Class A custom Handler 11687
Class B custom Handler 11687
Class A custom Handler 11779
Class B custom Handler 11779
Class A custom Handler 11863
Class B custom Handler 11863
Class A custom Handler 11951
Class B custom Handler 11951
Class B custom Handler 12035
Class B custom Handler 12119
Class B custom Handler 12207
...
[/as]
As you can see, as soon as attempt to remove ClassA and ClassC instances were executed, ClassC instance were immediately removed, but ClassA instance stays for several frames before finally removed. This late removal occurs a lot in real programs so there's no need to worry if memory usage does not drop immedialy after you remove some movie clips. A neat hack that is commonly used is to create a local connection and connect it to a non-existent connection. Please refrain from using this hack too frequently for garbage collection is always expensive.
[as]
try {
new LocalConnection.connect('foo');
new LocalConnection.connect('foo');
} catch (e:*) {}
[/as]
The last item that we'll examine is the Loader class (also apply to URLLoader). In actual fact, this examination doesn't have any real application, it's just something that might confuse some people so I want to address it.
Frame 6 code:
The code is nothing fancy. All it does is adding a new Loader object that loads an image on every frame and count the number of Loader objects in memory. Recall that earlier in this article, it has been shown that if we constantly add arrays to a weak reference dictionary, the number of objects stays at 1. As you might have deduced from previous tests on movie clips, Loader objects don't get garbage collected immediately.
Now run GCExample.fla on Flash IDE and click on "Test Loader", you'll find the number of keys increase indefinitely. To dramatize this a little bit more, you can try adding the LocalConnection hack to the end of the frame handler function.
[as]
...
var n:int = 0;
for (var key:* in countDic) {
n++;
}
mTxtInstanceCount.text = "Number of keys " + n;
try {
new LocalConnection().connect("foo");
new LocalConnection().connect("foo");
} catch (e:*) {}
}
[/as]
Strangely enough, even the outrageous code that force GC on every frame does not make the Loader objects go away at all. Rest assured, this is something that only occurs on the standalone Flash Player. If you run our example on a browser, for instance Firefox, the instance count remains at 2 or 3. And if you remove the LocalConnection hack, it may go up to over 100 before dropping back to 0. Apparently, this is only an interesting phenomenon to observe, not anything serious that needs to be taken care of.
That concludes this article. I hope it help answer/clarify questions/thoughts someone might have, as I did. I know I didn't write best code so please feel free to point out where it should be improved/corrected.