Wednesday, March 4, 2009

Flash Garbage Collection (IE, FF3, and BitmapData in AS3)

Ooooh Garbage Collection, how I love thee.

During the testing phase of a current project we had many reports of virtual memory errors. Faaantastic. So I load up Firefox and click around the site while watching my memory using Vista's task manager. Not much is happening. I click around to different pages. I click on every video. I try to click on everything as quick as possible. I try to sit through every video. Nothing. No spikes. Sure it jumps around here and there but nothing significant. My gut instinct is A. How much crap do these people have open on their computer? and B. How old are their computers?

Although those may have been valid questions, I was completely off. Turns out with some more digging that every person who had a problem was using IE. So I load up IE. I don't even have to click on anything and I can watch the memory climb all the way up to 1gig before it drops down to 100mb and starts its ascent again. Holy crap, 1 gig???

The culprit was easy enough to find... we have a reflection on the home page that is getting updated onEnterFrame. If the memory is going up and I haven't even clicked on anything, that has to be it. For the reflection we had 1 bitmapData that was getting disposed every frame and then set to a new BitmapData object. I didn't code this, but at first glance it seemed to make sense. The dispose should get rid of the data and then a new one is created. I knew from Moock's book that the Garbage collector works on its own timetable, but I had no clue that it would allow the Player to reach 1 gig before it ran. I also had no clue that it would behave differently in IE than it would in FF.

It was easy enough to fix. I noticed the width/height of the bitmapData was never changing so there was no need to dispose the old one and create a new one. We just needed to create one bitmapData in the constructor and then call the draw function onEnterFrame instead. Memory leak or not, this was much more efficient than destroying and creating a new object on every frame. This kept IE sitting at a steady 70mb instead of the whopping 1gig.

After this exercise I decided to brush up on my garbage collector knowledge. I highly recommend checking out Grant Skinner's articles: Understanding garbage collection as well as Resource management strategies in Flash Player 9.


One gem I took away was that you need to remove the enterFrame listener on any sprite that you are removing, EVEN if you did not add a listener yourself... I didn't realize that the built in listener could cause the object to stay in memory.


Anyways, it was a good exercise and I thought I would share. Morals of the story:
1. Test on multiple browsers and environments. You can't assume Flash will behave the same in each.
2. Don't create new objects when you can just alter an existing one.
3. A full understanding of how garbage collection works is very important and worthwhile to research.

11 comments:

Flo said...

Yeah you are right with these 3 points. That is the reason why I like the principle of Destructor Functions (kill all properties and remove all Listener of the component) and Object Pooling (http://lab.polygonal.de/2008/06/18/using-object-pools/)

Ickydime said...

@Flo
Great article on Object Pooling. I knew it was the way to go, but hadn't seen benchmarks before.

gludion said...

sorry, I don't understand how I could remove an enterFrame in as3 if it was not created by me..
- does it happen because of third part API (tween, etc..)?
- Then, how to you remove the listener if you don't know the listener function?
Thx in advance ;)

Ickydime said...

@gludion
Great question man. You got me stumped.

I am re-reading Skinner's article and maybe I am reading it wrong. He says:

"A game sprite subscribes to its own enterFrame event. In every frame it moves, the application performs some calculations to determine its proximity to other game elements. In ActionScript 3.0, even after you remove the sprite from the display list and null all references to it, the application continues to run that code in every frame until it is removed by garbage collection. You must remember to explicitly remove the enterFrame listener when the sprite is removed."

On the second read, it appears that the object will not stay in memory but it will continue to play. So I would assume removing any enterFrames that you created and adding a Stop to the animation would be sufficient.

... I'll update my post. Thanks for the great question.

Flo said...

GSkinner posted a nice Utility EventDispatcher Class, which provides the function removeAllEventListener(). Have a look here
http://www.gskinner.com/blog/archives/2003/09/code_gdispatche.html

Ickydime said...

@Flo
Looks like that is some old school AS2. But Still appreciate the post and link.

Jensa said...

I had something similar the other day and I found that if IE reaches 1.5Gb ram, it'll crash - at the request of the Flash Player (according to the crash dialogue) :-D

Took a little time to find the bug, but in general - FireFox is much more forgiving than IE when it comes to garbage collection. Isn't that a little odd actually? It's the same VM so how does that tie into the browser?

J

gludion said...

hello again ;)
@Ickydime: regarding Sprite's enterframes, Skinner was just hypothetizing them as examples (of real-world projects).
(The previous line says something like "here are some examples...")
Normally, Sprites don't have "automatic" enterFrames.

@Flo: a more "as3" swiss knife designed by G.Skinner is the Janitor class. At least a good base for further enhancements:
http://gskinner.com/talks/resource-management/ (Janitor class is in the ZIP file)

Flo said...

@gludion:
Thanks for the correction! :-)

Mike Morearty said...

Ickydime, I'm pretty sure the reason the garbage collector let the memory consumption get up to 1GB is because the memory taken by a BitmapData is not visible to Flash's garbage collector. The BitmapData's memory is separate -- it is lower-level operating system memory holding the bitmap, not a regular ActionScript object.

That is to say, there is a little tiny BitmapData object that is visible to Flash's GC -- I'd guess it probably takes just one or two dozen bytes -- but one of the things in that memory is a pointer to memory that was allocated by the operating system. For example, on Windows it might be an HBITMAP returned by a call to Windows' CreateBitmap function, or something like that. So the result is that Flash's GC thinks your program is using a lot less memory than it's actually using, and so it doesn't bother doing a garbage collection.

Anyway, the fix, as you discovered, is to find a place in your code where you can explicitly call BitmapData.dispose() at some earlier point in time, when you happen to know, because of the structure of your code, that the BitmapData is actually no longer needed. This is unfortunate, because in general, languages that have GC shield you from having to do this kind of stuff; but occasionally, when operating system resources are involved, it becomes necessary.

Ickydime said...

Mike, thanks for the information, I appreciate your insight.

It would make sense then if the browser was controlling the collecting in some manner. That would allow Firefox to stay relatively low while IE took off.

The only thing that doesn't add up is the dispose. We were calling it every frame and creating a new BitmapData. It seemed to only remove the reference of the old bitmapData so that it could be garbage collected, but didn't actually dump the memory... hence the leak. The fix was to just reuse the same BitmapData object.