ActionScript 3 has empowered Flash developers with faster code execution and a ton of API enhancements. Unfortunately, it has also led to the need for a much higher level of developer responsibility than ever before. In order to prepare and educate developers on how to deal with some of this new responsibility, I am writing a series of articles on resource management in AS3, Flex 2, and Flash 9. The first of these articles discussed the mechanics of the Garbage Collector in Flash Player 9. This article will focus on the implications some of the new features of AS3 have on resource management, and the potential headaches they could cause you even in simple projects. The next article in the series will introduce some of the new tools we have at our disposal to deal with these issues.
The biggest change in AS3 that affects resource management is the new display list model. In Flash Player 8 and below, when a display object was removed from the screen (with removeMovie or unloadMovie), it and all of its descendants were immediately removed from memory, and halted all code execution. Flash Player 9 introduces a much more flexible display list model, where display objects (Sprites, MovieClips, etc) are treated the same as normal objects. This means that developers can now do really cool things like reparenting (moving a DO from one display list to another), and instantiating display objects from loaded SWFs. Unfortunately, it also means that display objects are now treated the same as every other object by the Garbage Collector, which raises a whole slew of interesting (and possibly non-obvious) issues.
- Part 1: The FP9 Garbage Collector
- Part 2: Resource Management Issues in FP9
- Part 3:New Tools in AS3
- Soon: Strategies and Solutions
Issue 1: Dynamic Content
One of the more obvious issues is related to Sprites (or other DOs) that you instantiate dynamically, then wish to remove at a later time. Because display object's no longer live and die on the display list, when you remove the object from the stage it continues to exist in memory. If you have not cleaned up all other references to the clip, including the object's listeners, it may never be removed. If you have done a good job of cleaning up all references, the clip will be removed from memory the next time the GC runs a sweep, which is at some indeterminate point in the future based loosely on memory usage (see my previous article on Garbage Collection in AS3 for more information).
It is very important to note that not only will the display object continue to use memory, it will also continue to execute any "idle" code, such as Timers, enterFrames, and listeners outside its scope. A couple of examples may help illustrate this issue:
- You have a game sprite that subscribes to its own enterFrame event. Every frame it moves and carries out some calculations to determine it's proximity to other game elements. In AS3, even after you remove it from the display list and null all references to it, it will continue to run that code every frame until it is removed by garbage collection. You must remember to explicitly remove the enterFrame listener when the sprite is removed.
- Consider a MovieClip that follows the mouse by subscribing to the stage's mouseMove event (which is the only way to achieve this effect in the new event model). Unless you remember to remove the listener, the clip will continue to execute code every time the mouse is moved, even after the clip is "deleted". By default, the clip will execute forever, as a reference to it exists from the stage for event dispatch (we will look at how to avoid this in the next article).
Now imagine the implications of instantiating and removing a bunch of sprites before the GC does a sweep, or if you failed to remove all references. You could inadvertently max out the CPU fairly easily, slowing your application or game to a crawl, or even stalling the users' computers entirely. There is NO WAY to force the Flash Player to kill a display object and stop it executing. You must do this manually when it is removed from the display. I will examine strategies to manage this task in a future article.
Here's a simple example (Flash Player 9 required). Click the "create" button to create a new Sprite instance. The sprite instance will start outputting a counter. Click remove and note how the output continues, despite the fact that all references to the sprite have been nulled. You can create multiple instances to see how this issue compounds over the life of an application. Source code is available at the end of this article.
Issue 2: Loaded Content
If you consider that the contents of loaded SWFs are also now treated the same as every other object, it is easy to imagine some of the problems that you can encounter with loaded content. Just as with other display objects, there is no way to explicitly remove a loaded SWF and its contents from memory, or to stop it from executing. Calling Loader.unload simply nulls the loader's reference to the SWF, it still has to be picked up by a GC sweep (assuming all other references to it have been properly cleared).
Consider the following two scenarios:
- You build a shell that loads your experimental Flash pieces. This experimental work is cutting edge, and pushes the CPU to the limits. A user clicks a button to load one experiment, views it, then clicks a button to load a second experiment. Even if all references are cleared to the first experiment, it will continue to run in the background, which will likely max the processor out when the second experiment starts running at the same time.
- A client commissions you to build an application that loads AS3 SWFs created by other developers. These developers add listeners to the stage, or otherwise create external references to their own content. You now have no way of unloading their content, it will live in memory, and consume CPU until the user quits your application. Even if their content does not have any external references, it will continue to execute indefinitely until the next garbage collection sweep.
For security sensitive projects, it is very important to understand that if you load third party content, you have no way of controlling when it is unloaded, or what it executes. It would be VERY easy to write a SWF that once loaded into your application continued to execute in the background and captured / transmitted user input / interactions, even after being unloaded.
Another simple example. Exactly the same scenario as in Issue 1, but loading a SWF each time instead of using dynamic instantiation.
Issue 3: The Timeline
This one is mostly an issue for those playing with Flash 9 public alpha, and it is important to remember that it is alpha. Hopefully this gets resolved before beta or final.
The timeline in AS3 is code driven. It dynamically instantiates and removes display objects as the playhead moves. This means it is subject to the same problems as those listed in issue 1. Clips that are removed from the screen due to timeline updates will remain in memory, and continue to execute any idle code on them until they are picked up by the GC. Not really expected behaviour for Flash developers, and certainly not for Flash designers (who shouldn't have to think about GC at all).
Here's another quick example. Same concept, but this time it is jumping between two frames to instantiate / remove a clip.
What is Adobe thinking? Or alternatively, why is this an issue?
Flash Developers are likely looking at this, and thinking WTF, this is a nightmare!? On the other hand, Java developers are probably looking at it and saying "so what?". This disparity is understandable - Flash developers are not used to having to do manual resource management beyond basic best practices (ex. kill references when you're done), whereas Java developers have been through all this before. These issues are par for the course for most modern memory managed languages, and unfortunately there is no way to completely avoid them.
On the other hand, Flash raises a lot of challenges that are rare in other languages (including Flex for the most part). Flash content tends to have a lot of idle / reactive code execution, whereas Java and Flex are mostly interactive (ie. CPU intensive code usually only executes based on a user interaction). Flash projects load external content from third party sources (possibly with poor coding standards) far more often as well. Flash developers also have fewer tools, profilers and frameworks to utilize. Finally, Flash developers generally come from a much less formal programming background - most Flash developers I know have backgrounds in Music, Art, Business, Philosophy or just about anything but programming. This diversity results in AWESOME creativity and content, but does not really prepare the community for dealing with resource management issues.
Summary
Resource management is going to be an important part of AS3 development. Ignoring the issue could result in sluggish content, and potentially stalling users' systems completely. There is no longer any way to explicitly remove a display object from memory and stop its code from executing, which means we have a responsibility to clean up properly after our objects. Over the next few weeks I will outline some tools and strategies for tackling these issues. Hopefully as a community we can establish best practices and frameworks to make this transition easier.
You can download the source code for the demos by clicking here.
I will continue to update these articles with the latest information and community input as it becomes available, so you may want to check back occasionally.
Comments (32)
Damn. Good stuff to know. Guess we're all gonna have to grow up. Even us old guys. :)
Posted by: Keith Peters at July 5, 2006 11:10 AMURL: http://www.bit-101.com
Excellent article. Brings to light some non-obvious but incredibly important points.
Posted by: Ash at July 5, 2006 11:34 AMURL:
Great to know cheers. Although a bit scary. I blame Java. Can't even clean up after itself - sheesh :-)
Posted by: Mike at July 5, 2006 01:04 PMURL:
Great stuff. Sounds like system resource management is crucial moving forward. Thanks for staying on top of what's important.
Posted by: Michael DelGaudio at July 5, 2006 01:55 PMURL: http://michaeldelgaudio.com
Grant-great stuff. Makes me feel better that your researching these issues.
Posted by: ethan at July 5, 2006 02:33 PMURL:
Thanks everyone. These articles take a fair bit of work, so it's great to hear that they're useful to people. :)
Posted by: Grant Skinner at July 5, 2006 04:31 PMURL: http://gskinner.com/
Agree with everyone's comments. I've been porting over some of our code-based components from as2... A lot suprizingly translates very well, while there are a few gotchas. I do agree that keeping up on the display object references will certainly be a new burden. Thanks for bringing this to light.
Posted by: Ian T at July 5, 2006 06:19 PMURL:
Excellent article--I learned a lot!
Posted by: Robert Penner at July 5, 2006 11:37 PMURL: http://robertpenner.com
I'm so happy, all my time spent worrying about memory usage and damning Delegate as evil have made me well prepared for all this.
Here's how you do it, one simple rule. If an instance of a class creates another instance of a class, it must also destroy this class. By destroy I mean tell the class to remove any variables that might keep it alive. Unloading should occur exactly opposite to loading.
That's how I do it anyway. It makes it easier to keep everything under control on larger projects.
Posted by: Max at July 6, 2006 03:22 AMURL:
Great article Grant !
Posted by: Thibault Imbert at July 6, 2006 04:57 AMdying for the strategies now ;)
URL: http://www.envrac.org
excellent article. thanks!
Posted by: good@very.com at July 6, 2006 11:23 AMURL: http://none
Interesting article Grant; especially the hint at "Issue #4" is a matter of non-programming background people who have yet to deal with garbage collecting. I've known of GC in the past but thought I was just lucky not to deal with the level of effort you are talking about.
Posted by: Mark Lapasa at July 6, 2006 11:54 AMURL: http://knowledge.lapasa.net
Another great article :).
But, have you test with the weak reference flag set to true in addEventListener ?
Posted by: Cédric Néhémie at July 7, 2006 02:28 AMURL: http://book.abe.free.fr/blog/
Cédric,
You're still jumping the gun. Weak event references will be in the next article. ;)
They wouldn't help with any of the issues above though, except for content never getting picked up by the GC because of a strong event reference.
Posted by: Grant Skinner at July 7, 2006 09:07 AMURL: http://gskinner.com/
I really hope the timeline problem will be at least improved before release. A quick test showed that while using actionscript to add or remove movieclips trigger flash.events.Event.ADDED and flash.events.Event.REMOVED, movieclips added and removed with keyframes in the timeline does not trigger those events. So it seems it isn't even possible to use a listener to clean up?
Posted by: Tore Jørgensen at July 13, 2006 03:41 AMURL:
Great article. Looks like there are some big potholes in the road. Nice to have some advance warning. Best practices, although important now, will become a must.
BTW. I know FP9 is alpha but, on the loading example I get this error in a dialog box:
Posted by: Andrew E at July 13, 2006 03:38 PMError #2044: Unhandled IOErrorEvent:. text=Error #2036: Load Never Completed.
URL:
Keep up the great education you are giving us flash people who have been pampered till flash 8 and suddenly shown the real world by MM/Adobe :)
Posted by: Supriya at July 19, 2006 05:32 AMURL:
Hi,
in yor first article, you were talking about two mechanisms of GC. The first one was that simple counting of references. Has this been disestablished in Flash Player 9? I ask myself, why Flash Player 9 doesn't have both mechanisms, so people could avoid having problems, if they really would kill all references.
Posted by: Sven at July 23, 2006 03:16 AMURL:
nice
Posted by: jack at July 25, 2006 08:21 AMURL:
Hi All,
I have been reading through all of this resource managment information and it's wonderful thing to be kept up to par. I have some questions and don't want to clutter this blog. If anyone, Grant feels like they could chime in could they discuss some of my views and help me to clear up some of my perceptions. My blog relating to this topic is at the following URL: everything4me.com/ComSvr/blogs/sample_weblog/archive/2006/08/02/24.aspx
Posted by: Metallikiller at August 2, 2006 12:03 AMURL: http://www.everything4me.com/comsvr/
Great articles Grant, much learned, thanks so much!
Posted by: nwebb at September 1, 2006 09:03 AMURL:
hi thank about the article
Posted by: mohammed barqawi at September 14, 2006 05:05 AMi finished developing a KIOASK application using flex and i found that if i run the application for one hour it eat all 50 mega from the ram , i am stuck , how should fix that
URL:
Nice article. Tore Jørgensen's post brings up a point that I am trying to solve, unsuccessfully. For whatever reason, MovieClips on a keyframe do not dispatch Event.REMOVED events. If you have a MovieClip on frame 2 of the main timeline, and a blank keyframe on frame 3, only 1 Event.ADDED event gets dispatched on frame 2, with the event.target being a "Shape" object inside that MovieClip, and no event.REMOVED events at all. Very troubling...
Posted by: Jesse at November 8, 2006 12:17 PMURL:
Hi,
I have this strange GC problem with the loader class;
When I load an image and unload it later on. It won't be disposed by the garbage collector.
There are no references to neither the Loader nor the (weak) eventlisteners. After loading the image a couple of times the memory goes sky high :(
Here is a (simple) example:
//this example loads an image, unloads it as soon as it's loaded, and loads another one....
package {
import flash.display.Sprite;
import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;
import flash.system.System;
public class Application extends Sprite{
private var currentLdr:Loader;
private var request:URLRequest;
function Application(){
this.request = new URLRequest("http://www.flashfocus.nl/forum/image.php?u=8105&dateline=1182191022");
this.loadNextImage();
}
private function loadNextImage(){
this.currentLdr = new Loader();
this.currentLdr.contentLoaderInfo.addEventListener(Event.COMPLETE, this.loadCompleteHandler,false,0,true);
this.currentLdr.contentLoaderInfo.addEventListener(Event.UNLOAD, this.unloadCompleteHandler,false,0,true);
this.currentLdr.load(this.request);
trace("Current mem: " + System.totalMemory);
}
private function loadCompleteHandler(event:Event):void{
this.currentLdr.unload();
}
private function unloadCompleteHandler(event:Event):void{
this.loadNextImage();
}
}
}
//
Do you have an explaination on why de GC doesn't clean the loaded content?
Hope you can help me out... tnx!
PS: as you already mentioned in you post, using the unload method on the loader does not affect the memory. :(
Posted by: Arno van Oordt at June 18, 2007 01:23 PMURL: http://blog.justgreat.nl
PPS: here you can find the complete example:
Posted by: Arno van Oordt at June 18, 2007 01:43 PMhttp://blog.justgreat.nl/wp-content/uploads/2007/06/loadertest.zip
URL: http://blog.justgreat.nl
I found the solution on in this post:
http://www.webforumz.com/macromedia-flash/42690-memory-leak-when-loading-swf-in.htm
It seems that the unload() method has a bug when used in the flash IDE. Once I ran the test in a browser there seems to be no problem and the GC nicely clears the memory...
Hope this will help people who had the same problem.
Posted by: Arno van Oordt at June 19, 2007 03:15 PMURL: http://blog.justgreat.nl
Simply great article... cleared lot of points about AS3. Looks like you have lot of RnD. Thanks grant
Posted by: inder at August 14, 2007 02:11 AMURL:
Thank you for the useful informations.
Posted by: Bank zdjec at August 16, 2007 04:40 PMURL: http://www.buy-photo.eu
Hi! Very helpful article!
But... don't you think it would be a big help if they offer a method to entirely make dissapear the DO, its childs and event listeners?
It would be much easier and GC skills would still be useful.
Greetings from Argentina!
Posted by: Ces at August 16, 2007 08:23 PMURL:
This appears to be an even bigger problem with AIR applications. Calling the double localConnections doesn't seem to cause the GC, and it seems like the GC doesn't run at all on its own either. I've got all my listeners set as weak, but the memory just keeps going up, never down. I start at 32K and can jump to 120K in just a few minutes.
Posted by: Jason at August 17, 2007 12:32 AMURL:
GC is with as3 is complete crap. Sophisticated languages that need sophisticated memory management, need sophisticated objects to manage memory! AS3 nothing in the way of memory management! I'm loading external swf's (which contain 300+ PNG sequence, also where Flash absoloutly FAILS with) which ramps the ram usage to 300 megs per swf (GOD I HATE FLASH). So aside from growing past 2 gigs of usage, I decided I should unload the swf's everytime they end (ready to start again) in hopes of this application actually running. However- with ZERO references to the dynamically loaded (with Loader) swf's, unload() does NOT trigger any GC of anykind- ARRRRG
Posted by: DivineAnarchy at December 21, 2007 09:58 PMURL:
Great article, thank you. I am running into these problems myself... I have a series of linked animations playing, each with attached addFrameScripts on their last frame... and sometimes the removed clip's framescripts are triggering during the newer clip's animation...from the grave as it were. setting the framescripts to null as they are removed does not help. very frustrating. looking for solutions!
Posted by: Sake Boy at March 11, 2008 11:03 AMURL: