The Situation

Your Tweens aren’t finishing. Or your URLLoaders won’t load. Or your Timers stop ticking.

The Problem

Consider the following function:

function doStuff():void
{
	var x:Object = {};
}

When this method is called, it creates an object and stores a reference to it in the variable x. After the function finishes executing, there are no remaining references to the object, so the Flash Player clears it from memory (as soon as it gets around to it). Of course, this happens no matter what type of object we create.

Unfortunately, there are some situations where this behavior can cause major problems. Objects like Tween, Loader, and Timer are associated with asynchronous processes (animating, loading, etc..) that we expect to complete regardless of whether the objects that originated them are still in the player’s memory.

Sometimes, we’re lucky. Depending on the how many things your application is trying to do at once, the garbage collector might not perform a sweep while our Tweens are playing (or our URLLoaders are loading). Still, it’s impossible to anticipate exactly when the garbage collector will do its thing, and we certainly can’t allow it to decide whether our applications will function correctly or not. Therefore, we can never ever use these classes without storing a reference to each instance we create.

However, when the number of objects is dynamic (or you’re simply making a lot of them), this can get to be a hassle pretty quickly. This problem is illustrated by the following (admittedly outlandish) function, which creates 1000 Sprites and uses 1000 Tweens to fade them out.

function doSomething():void
{
	var mySprite:Sprite;
	for (var i:uint = 0; i < 1000; i++)
	{
		mySprite = new ItemInTheLibrary();
		this.addChild(mySprite);
		mySprite.x = Math.random() * this.stage.stageWidth;
		mySprite.y = Math.random() * this.stage.stageHeight;
		new Tween(mySprite, 'alpha', None.easeNone, 1, 0, 1, true);
	}
}

In its current state, this function just won’t work. (At least not on a MacBook Pro with 2GB of RAM.) All of the Sprites will be created and added to the stage, but few (if any) of the Tweens will play. By not storing any references to our Tweens, we authorize the garbage collector to dispose of them as soon as the Flash Player needs the memory for something else. Pushing each Tween instance into an Array would solve our problem (try it), but then we’d have to worry about removing them from the Array when the animation finished (or else leave 1000 unused Tweens hanging around in our computer’s memory).

The Fix

My solution is a set of classes (GCSafeAnimator, GCSafeLoader, GCSafeTimer, GCSafeTween, and GCSafeURLLoader) that prevent themselves from being garbage collected while their respective processes are underway. For better or worse, each class has the exact same API as its Adobe counterpart. So to create the Tween in the above function, for example, you would type:

new GCSafeTween(mySprite, 'alpha', None.easeNone, 1, 0, 1 true);

Similarly, loading a url would look like this:

var ldr:GCSafeURLLoader = new GCSafeURLLoader();
ldr.load(new URLRequest(‘http://exanimo.com’));

The packaging is also based on that of the Adobe classes (so GCSafeURLLoader is located at com.exanimo.net.GCSafeURLLoader and GCSafeTween is located at com.exanimo.transitions.GCSafeTween). Of course, when an object is no longer performing its task (be it tweening, loading, or whatever), it again becomes eligible for garbage collection.

Download the GCSafe examples and source as a zip file or from my as3 subversion repository (AS3 only).

Most people probably assume that loading a url with Flash’s URLLoader will eventually result in either a “complete” event or an error event of some kind. It also seems reasonable to assume that, if you create a Tween and leave it alone, the animation will play to completion. However, if you aren’t careful, these objects can be snatched up by Flash’s garbage collector before they’ve finished, leaving your application listening for an event that will never come. It’s for this reason that I can’t see myself ever touching Flash’s Tween, URLLoader, Loader, Timer, or Animation classes again. The "GCSafe" versions of each easily fill the shoes of their Adobe counterparts while eliminating the uncertainty brought on by an overzealous garbage collector. They’re available under the MIT License.


Comments

Gravatar geng

Hi Matt,

Thanks for the info! I am curious. Why do you declare the Dictionary object in GarbageCollectionShield class at compile time instead of at run-time?

Geng

Gravatar matthew

You mean why is it static? That’s a good question. I actually wasn’t sure about how to treat that, and it may change. However, there’s only one situation in which it matters: when one Object is added to multiple instances of GarbageCollectionShield, and then removed from one instance. Should the Object then be freed for garbage collection? Or must it be removed from every instance of the GarbageCollectionShield to which it was added?

When I made these classes available, I kind of figured that GarbageCollectionShield was a class only used behind-the-scenes and it didn’t much matter, but any thoughts on its API are certainly welcome. If the consensus is that it should handle things differently, I’ll change it.

Gravatar soulwire

Hello. Great work!

I have been looking through your classes, and theoretically there should be no noticable memory increase through using your shield. I was wondering tough, have you done any tests?

I have been looking at this thread: http://www.kirupa.com/forum/showthread.php?t=275524 perhaps it is of some relavence for an updated class which is both noticably faster for events and shields from the GC?

Good work again, thanks for sharing :)

Gravatar Dan

Thaaaaaaaaaank-you!

Am new to flash, but not to programming, so when my first piece of development work started randomly falling over, I didn't really know what to do. If it works once, it should continue to work! Wasn't thinking about garbage collection though :S

But, I'm amazed that it's a viable scenario for Adobe that Tweens etc can be garbage-collected before they have finished playing. At the least you would think they would feature a fairly prominent warning in the documentation, but there you go.

Anyway, great work. More people need to know about this though so I shall add Google-fodder in the form of my first search: "why won't my stupid tweens work". Hopefully some more desperate souls will stumble upon your work.

Cheers!

Gravatar Phillip Knezevich

This fixed my problem. Nice work.

Why the hell haven't Adobe deal with this..

PK

Gravatar Doug

Thank You! I've been looking for a solution for that for weeks!

Doug

Gravatar Tomek

It was really easy to apply the changes in my code.

Thanks

Gravatar Darkriderdesign

Dude thank you, this has been driving me completely crazy lately. I have seen a lot of online examples on how to fix this, but none of them seemed to work across the board for many situations. It seems Action Script 3 has now become very powerful, but at the cost of us all having to relearn some of the basics.

Rockin thanx again,

Darkrider

Gravatar Matthew Trainer

Thanks a ton! This saved me some massive headaches I've had for weeks.
You da man!

Matt

Gravatar Darcey

Well it may be just me but I see a logic as to why it wouldn't work.

Creating a loop which:

1. creates an object using a static name (never changes)
2. create a tween and apply to the static object name

Repeat

The problem is the use of the static object name. I'm supprised that any tweens work apart from the last 1 in the loop.

The displayObject works differently and thus adds objects (sprites in this case) in a different method than how the tween class works. Probably tracing out indexes and display class object ids and names would give a better picture as to what is going on.

Solution: Give each tween it's own object name to attach itself to.

import fl.transitions.*;
import fl.transitions.easing.*; // Can be removed if fl.motion.easing is left in
import fl.motion.easing.*; // Can be removed if fl.transitions.easing is left in

function doSomething():void
{

var mySprite:Sprite;
var myArray:Array = new Array();

for (var i:uint = 0; i < 1000; i++)
{
myArray[i] = new mcBox();
this.addChild(myArray[i]);
myArray[i].x = Math.random() * this.stage.stageWidth;
myArray[i].y = Math.random() * this.stage.stageHeight;
myArray[i].tween = new Tween(myArray[i], 'alpha', None.easeNone, 0, 1, 1, true);
}
}

doSomething();

Gravatar matthew

@Darcey:
Yes, there is a logic to why my loop doesn't work, but I'm not sure you've gotten it quite right. As I explain in the article, it's the logic of garbage collection; to make sure that each tween completes, you must ensure that there's a reference to each until they've finished. Therefore, it doesn't really have anything to do with "using a static name" per se, but the number of variable references.

At first glance, your example seems a lot like my suggestion to "[push] each Tween instance into an Array"; however, because your Array is defined inside the function (and no references to it are stored after the function completes), it too would be garbage collected, leaving no references to your Tweens. Without any references to them, they would be snatched up by the garbage collector and stop working.

Of course your code does work. Why? Well, if you examine it a bit closer, you'll realize that you're in fact not storing references to the Tween in an Array. Instead, you're storing your reference on an item in the Array - namely, your Sprite! The display list contains a reference to your Sprite, since it's been added to the stage, and your Sprite in turn contains a reference to your Tween. Thus, it's protected from garbage collection. The Array, however, is not, and will be garbage collected after the completion of the function. In other words, the Array isn't even used and your function is exactly equivalent to this:

function doSomething():void
{
  var mySprite:Sprite;
  var myArray:Array = new Array();

  for (var i:uint = 0; i < 1000; i++)
  {
    mySprite = new mcBox();
    this.addChild(mySprite);
    mySprite.x = Math.random() * this.stage.stageWidth;
    mySprite.y = Math.random() * this.stage.stageHeight;
    mySprite['tween'] = new Tween(mySprite, 'alpha', None.easeNone, 0, 1, 1, true);
  }
}

This technique works and was used by many in AS2 programming. However, it really isn't very good practice for a few reasons. First, notice that I don't use dot syntax to access the tween property of mySprite. That's because Sprite is a non-dynamic class with no tween property, so the compiler would throw an error if I tried. This is kind of a hack to bypass the rules that the class defines. Your example does the same thing, but it's somewhat hidden: since accessing items in an array returns an untyped reference (*-typed), you can set any property you'd like on the item, even if it's not defined by the class. Secondly, setting the tween property on your sprite could have unintended consequences. Suppose elsewhere in your application, you (or somebody else who isn't aware of what you did here) use the same technique to set a Tween on a different property? The tween property of your Sprite would then point to this new Tween, and there would be no reference to the original alpha Tween. The sprite would stop fading and the developer would be left with the inenviable task of determining why one Tween should cause another to stop sporadically.

Hopefully, this will clear some things up about the original article, and about garbage collection in general.

Gravatar Darcey

Yep…

Just placing the tweens into an array for seperation purposes and lets them be garbage collected on completion.

For more complicated usage I wouldn't go anywhere near this code…..

Not sure about attaching the tween to the spirte object, but it all works :)

Something I tinkered up a while back and posted up on my site for online reference which may be of use to people reading this page.

http://www.allforthecode.co.uk/forum/viewtopic.php?f=8&t=166

Gravatar matthew

Actually, "placing the tweens into an array" does not allow them to be garbage collected upon completion. Even after they've completed, the Array will still contain a reference to them. Therefore, they won't be garbage collected and will remain in memory.

In any case, this isn't what your code is doing. As I tried to explain above, your code is in fact "attaching the tween to the sprite object" - not "placing the tween into an array."

The Sprite is created and stored in an Array in the following line:

myArray[i] = new mcBox();

At this point, the Sprite is stored at myArray[i]. Any occurrences of myArray[i] are simply referencing that Sprite. So when you type the following:

myArray[i].tween = new Tween(myArray[i], 'alpha', None.easeNone, 0, 1, 1, true);

what you are actually doing is 1) retrieving a reference to your Sprite, 2) creating a Tween instance, and 3) storing a reference to the Tween on the Sprite object itself. This is exactly the same as the following line in my comment above:

mySprite['tween'] = new Tween(mySprite, 'alpha', None.easeNone, 0, 1, 1, true);

If you're still not convinced, try adding the statement trace(this.getChildAt(this.numChildren - 1)['tween']); in your for loop. This will trace the tween property of the most recently added Sprite.

As I mentioned above, there are several reasons why this would not be a good idea. One that I didn't mention (but that your comment has reminded me of) is that none of these Tweens will be garbage collected until the Sprite they're acting on has been removed from the stage and all references to it are lost. (Again, it's the Sprite instance that's keeping your Tween from being garbage collected so as long as the Sprite exists, so will the Tween - unless, of course, you overwrite the Sprite's tween property.) That means that 1000 unused Tweens will be taking up space in memory!

Does this make things any clearer?

Gravatar Darcey

I'm fine with the code, my code was just a quick response for others to hot wire, try out, modify, discuss etc.. Also a quick method of getting the original example running. Thought some may find it interesting and also beneficial especially with your replies and breakdowns of the code posted. :)

If I were to code something up for this purpose I would probably be stemming everything from:

private var mcArray:Array;
private var mcTweenArray:Array;
//Maybe even use a multi dimensional array for a single array reference, all depends on the coders pref'

Then manage what the target functionality is from there, with probably stage / status variables for position in the tween animation stages for flow control with possible interactivity disabling while tween is in progress etc.

Gravatar Nick

I don't think this is accurate. Your example doesn't work because the Tween class explicitly doesn't actually create its internal Timer until "later", and even when it does, it adds its event listener using a weak reference. If you, for instance, modify the Tween class to add its listener in the constructor, AND make it non-weak-referenced, AND start the timer immediately, then it will work.

Basically what's happening is, the Tween class is explicitly not letting the system keep it alive. If the Timer is started and the event listener is not weak, then the class will (correctly) survive.

I think this is a fault of the Tween class - its design means you have to store a reference to it for it to work reliably.

Gravatar matthew

Actually, the Tween class does create and start its internal Timer immediately. The constructor creates the Timer on line 433 and starts it on the next line via a call to start, which in turn calls startEnterFrame, which starts the Timer. Therefore, the only part of your scenario that's necessary is to "make [the listener] non-weak-referenced". In this situation, that means that Flash will keep a reference to the the instance's timerHandler or onEnterFrame method (depending on the value for useSeconds). Since the methods store an internal reference to the instance to which they belong, Flash maintains a reference chain to the Tween instance itself, preventing it from being garbage collected, as I described in the article.

So I'm not exactly sure what part of the article you think is inaccurate. I agree with you when you say "the Tween class is explicitly not letting the system keep it alive" and "its design means you have to store a reference to it for it to work reliably" (I said the latter almost verbatim in the article — in bold no less!). However, as I explain above, the reason that this happens is because no reference to the Tween instance is maintained. There are many ways to alleviate this issue (i.e. store a reference to the Tween): add one of its methods as a listener to another object's events (as per your suggestion), store it as a static property on a class (similar to how most 3rd party Tweening libraries work), put each instance into a Dictionary (like the GCSafe classes I provide), etc. It doesn't matter; they're all doing the same thing.

However, IMO, tinkering with the Tween class as you suggest is a very bad idea. It violates the open/closed principle and requires those using your code to know that they must use a modified version of the bundled Tween class. Somebody with a fresh install of Flash will be able to compile your project, but their Tweens won't work! They'll have no way of knowing that they need to dive into Adobe's classes and tinker with stuff. Conversely, by extending the class (as I did with GCSafeTween), everything will either work smoothly (if they have the GCSafeTween class), or they'll get a compile error telling them they need the GCSafeTween class. Both of these are much preferred.

Whether or not this is a fault of the Tween class is open for debate. Most, I'm sure, will agree with you, but there is definitely an argument to made to the contrary.

Gravatar ed

you rule!

Gravatar Eric Hague

Thank you so much. I've been having this problem ever since I started using the Tween class and was all set to make a human sacrifice to the Flash Gods when I came across your work.

Gravatar viva

hi, I've been coding using action script 3 and using the TransitionManager class to do some effects to my objects. Unfortunately, sometimes the effects just stop in the middle (incomplete) e.g a bouncing effects, or iris effects. Do you think this is caused by the GC?

Gravatar James

Hi

I didn't use your classes but you did help me figure out why my Tweens were randomly crashing.

Thanks a bundle!

James

Gravatar Anthony Brady

I have an XML slideshow that infinitly loops through various swf files which in turn have their own xml data feeds for things like phone stats etc. The problem I have is that the memory of the flash player increases everytime an swf file is loaded in. I have downloaded your packages and used your methods and the memory still grows, any ideas?

Gravatar Pedro Mendes

You're the boss!!! You solved the biggest headache in my project! Thanks a million and keep up the good work!

Gravatar matt

holy moly
thank u soooo much
yet another example of how either:

adobe flash employees are overpaid
or
macromedia left a legacy of poor code
or
both

regardless….u saved my butt major…..
u rulz

Gravatar Flash Tutorials | AS3 Classes Roundup Part2 | Lemlinh.com

[…] off, I will say that without Matthew Tretter’s garbage collection classes this demo would not be possible…While generating this many “particles” on ENTER_FRAME, the […]

Gravatar PerfectlyNormal

Awesome! This problem has been a real pain in the ass for a long time now. Assumed it was I who did something wrong, so never even tried to google for it. Thanks a lot for these fixes


Leave a Comment