[AS3] Some observations regarding events
Hello. I think you'll all find this interesting, please read on.
I've been peeved by the AS3 garbage collector, which has seems to often neglect to do its job. Until just now, I never could make an AS3 program that had no memory leaks, and now I know why. I've made some observations using a very basic Flash program, and I'd like you to conduct the test on your own machine and see if you get the same results. If you do, there are some pretty serious implications.
You can download my test program here: barebones.zip (http://www.rezmason.net/forum_resources/barebones.zip). I recommend that you read the code in it before compiling.
Flash lets you keep track of your program's memory usage by getting the value of System.totalMemory. If you notice that your program slows down over time, you can try writing a function that traces System.totalMemory every once in a while. But if that function contributes to Flash's memory usage, then the value of System.totalMemory will always go up.
I've discovered that the GC does not clean up some (or maybe all) events. An event is like any other object, and when it occurs, it takes up memory. And it looks as if the GC never touches this memory again! This means if you handle an event that happens over and over (like Timer or EnterFrame) or happens often (like MouseMove), event objects will keep being made and not deleted,and that's a memory leak.
In the barebones.fla file, I've included two functions that track System.totalMemory and two more functions that print the data. Try using each of the three addEventListener lines of code toward the bottom (one at a time, of course) and running the program using print1();. Every time Flash needs another page of memory (4096 bytes, something you can observe yourself by modifying candidateB), it will output the System.totalMemory, followed by the iteration of the event you're testing. If you run this Flash program again and again, the data it's outputting should always be the same for the same event– regardless of Flash's FPS, the speed of the timer, the size of the stage, etc.! The text file "memoryUsageStats.txt" holds statistics I've taken from barebones.swf running on my machine; see if they match yours.
So different events have different sizes, but their size isn't affected by how often they occur. And because System.totalMemory for barebones.swf never decreases, we can conclude that the events aren't being cleaned up by the GC. If my results can be repeated on other computers, then this means any AS3 program that updates constantly, like games, has a memory leak that cannot be circumvented.
One final observation I made is that the timeline in AS3 still has a memory leak of its own. In barebones.fla, try making an animated movieclip that contains a lot of shape data, and put it on stage. Then compare its memory statistics with the same program, but with a stop(); action in the movieclip's first frame. I expect you'll find that the animated movieclip leaks memory, too, so any Flash program with animation in it will leak memory, and there's no clear way to stop that, either.
Comments would be appreciated. C:-)
EDIT: The above phenomena are, in fact, not memory leaks; they're garbage that are (for the most part) deallocated when the totalMemory reaches Flash's ceiling.
For a real memory leak to occur, the active memory of the Flash Player must be increasing when it doesn't need to. This situation arises when objects are not properly dereferenced, thereby preventing the GC from removing them.
Thanks for your class, it show me how slow my game was
I have no problems whatsoever. No matter what, flash is always allocated an amount of memory, and it deals with that. It doesn't matter if your application doesn't even get close to that ceiling.
True, but some people's programs (such as mine) reach that ceiling frequently. All my event listeners use weak references, but my point is that this only keeps memory leaks from resulting from event listeners, and doesn't help prevent any other sort of memory leak.
EDIT: I'd like to add that programs that use systems that rapidly produce mounds of garbage, like ones that include complex animation, will quickly reach the ceiling, often dozens of times per minute. So the rate of garbage accumulation, and thus the significance of all this stuff, depends largely on what it is your program is doing, as well as how it's doing it.
I am very interested in your findings. Please update this post if you find anything else. Also, do you have a blog?
Here we go! This is my ActiveGraph class, which extends Sprite. Based on how you implement it, it'll trace your program's active memory in the Output panel, or display it on the screen with a small line chart. The chart scales to focus on the last 20 active memory readings; its only real use is to see whether there's a stable (wavering) or upward (memory leak) trend. Which is pretty handy! :D
Since System.totalMemory always returns large, hard-to-interpret numbers that are actually multiples of 4096 (a memory page), ActiveGraph represents memory in terms of pages, not bytes, so the numbers are easier on the eyes.
Here's the constructor. All the parameters are optional:
ActiveGraph(interval:Number,verbose:Boolean,visibl e:Boolean, degree:int);
interval is the number of milliseconds between memory checks. (Default is 120.) There's no reason to make this too small, but reducing the interval will technically make the ActiveGraph readings more accurate. Changing interval will not noticeably change the frequency that ActiveGraph updates; that's dependent on the GC, after all.
verbose, when set to true, outputs the active memory readings to Output. Just in case you want to look at the data in Excel or something. (False by default.)
visible determines whether ActiveGraph should bother updating the graph at all. If you only wish to use ActiveGraph in verbose mode, setting visible to false might improve your program's performance (doubtful). (True by default.)
degree is the number of GC strategies to compensate for. (Default and max are 2.) Since the GC uses two strategies to clean up garbage, you can set the degree to compensate for the quicker one, for both of them or for neither of them. By default it compensates for both, but if you want to plot the sawtooth graphs to assess your program's accumulation of garbage, you could lower the degree.
Give it a whirl! Let me know what you guys think. You have to be patient with it, it won't budge unless you have a significant memory leak. Try to make a leak, and then observe it with ActiveGraph. Note that you can drag the graph around if it's visible, in case it's in the way of something important.
EDIT 1.3: now compensates for both GC strategies.
EDIT 1.4: better compensates for the incremental nature of the mark'n'sweep strategy.
http://blog.739saintlouis.com/2007/03/28/flash-player-memory-management-and-garbage-collection-redux/
So if you never delete objects yourself, weak references are only a good practice. That is, unless you want to use the gBlog's weak reference tools in every AS3 project ever. :/ I'll pass, thanks.
You know, I just realized something creepy. :ponder:
In the past, when we'd use Flash, we'd treat Macromedia like they knew what they were doing; that's because Flash, and the code we'd write for it, weren't very powerful, and were limited. Macromedia kept a lot of these things out of sight, and we didn't give it any thought. AS3 hasn't just changed what we can do with Flash, and what can go wrong in Flash, it's also changed how we think of Adobe/Macromedia. They're people, like us, who can make mistakes, and more frequently now they'll rely on some of us to point out errors that they can correct during the next update. That's the reality for most complicated programming systems, and the Flash platform is no longer an exception, is it?
Can you give some more in-depth instructions on getting it to work?
An easy way to minimize memory leaks is to make sure your code never creates new instances of anything, except during instantiation. What I mean by that is, if you have a program that uses an object, rather than deleting the object and making a new one, you can reassign values to the properties of the object. That way, Flash won't have to allocate or deallocate anything once the program is done setting itself up.
I'd really like someone from Adobe to talk about this stuff, though. Does anyone known an Adobe programmer who likes to be bothered by Flash developers? :trout:
I have no problems whatsoever. No matter what, flash is always allocated an amount of memory, and it deals with that. It doesn't matter if your application doesn't even get close to that ceiling.
And I always use weak references with my events. Its not that hard.
myClip.addEventListener(Event.COMPLETE, onComplete, false, 0 , true);
the true at the end says that this should be a weak reference. Of course there are cases where you wouldn't want it to be weak (especially if it is the ONLY reference to the object). But most of the time, I think you should always make it a weak reference.
What sorts of errors are you getting now? ActiveGraph is based on standard classes from the flash package library, so I'm not sure what's up. Maybe with your help I can reproduce your issue and solve it.
Assuming that Flash knows what it's doing, the easiest way to observe memory leaks is to use the following code:
var n:Number, lastN:Number = 0;
var t:Timer = new Timer(100);
t.addEventListener(TimerEvent.TIMER, checkMem, false, 0, true);
.
.
.
function checkMem(e:Event=null):void
{
n = System.totalMemory;
if (!lastN lastN > n) {
trace(n);
}
lastN = n;
}
This code will trace the total memory of the system, each time the total memory goes down (that is, each time the GC sweeps). At these intervals, there should be almost no garbage in memory. So this code actually traces the active memory that your program is using.
If the numbers traced to the Output panel waver– that is, if they go up and down, but on average stay pretty much the same– then you have no memory leak. If the numbers steadily increase, you've got a leak somewhere, possibly caused by Flash but most likely caused by your own code.
Add the code above to a class that extends TextField, make some small changes and you've got yourself a nice gauge. Extend Shape instead, make some other changes and you could actually get a nice graph going with little overhead. That's what I'll be trying next, simply because the GC sweeps so infrequently that it's sometimes hard to notice an upward trend.
When I finish making the class, I'll upload it and let you all download it, and I'll probably mess with this thread a little to direct readers to the download link. How would you folks like that? :?)
Flash is a virtual machine and code interpreter, and when the player is first run, the instructions required to run the machine and to parse Actionscript are loaded into memory. After that, every instance of the player runs as a separate process- from what I take of it, every Flash file you open is a new instance of the Flash VM.
Memory, in general, supposedly works as follows; every time you run a new process (like a Flash VM instance), that process can ask your OS for more memory, but can never actually relinquish the memory to the OS until the process is killed (which happens when you load another web page, close a Flash Player window, etc). So the memory used by a Flash VM will never "go down".
What does go down is the amount of memory actually being used by the Actionscript code. The Flash VM pretends to be a computer more or less, and can allocate more memory to your Flash program. When you create garbage, the Flash VM treats it as important memory, and doesn't overwrite it- that is, until the total memory (active memory plus garbage) maxes out the memory your OS gave the Flash Player. Your OS might then allocate more memory to the Flash VM. But more importantly, this is when the Flash VM's garbage collector kicks in, finds out how much of the total memory is garbage, and "deallocates" it.
So the GC doesn't do anything on the OS level, it just cleans up the memory Flash is already using. That's the reason why the GC can't be "counted on"– it only kicks in when the System.totalmemory reaches the Flash VM memory's "ceiling".
It's actually really easy to "trigger" the GC- just make a while loop that makes and deletes an object, until the last observed value of System.totalMemory is less than the current one. That'll actually get rid of all your garbage! But there's a problem; each time the ceiling is reached, the OS might then give the Flash VM more memory, so the ceiling goes up, so the GC requires more garbage to max out, so the OS gives you more memory, etc. and you have a memory leak after all. Technically, this happens even when you're not maxing out the memory, just at a slower pace. :-/
However, we don't know how frequently the OS allocates more memory to the Flash Player. It would be nice if the Flash Player had a setting for the amount of memory it can ever ask for before crashing, but I don't see that option anywhere. I guess the idea is to trust the OS makers somewhat for managing memory, and to trust Adobe somewhat for properly programming Flash. If both of them did a good job, then on average, Flash's memory usage shouldn't increase at all.
All of this is relevant, because it represents a series of hurdles when you're trying to track down a memory leak in your program. The first method that comes to mind, which is regularly tracing System.totalMemory to the Output panel, will tell you nothing about your active memory usage, which is where the important leaks– the ones that you can stop– really happen. You can piece together a picture of what your active memory is like, but you'll always get only an approximation, which makes it difficult to track the leak down. More elaborate methods of observing the VM's memory usage have the same problem.
It's impossible to measure the active memory precisely, but I guess it's unimportant, as long as System.totalMemory decreases sharply at some point in time. Since these event objects are so small, it could take a very long time for the garbage collector to decide to sweep them; but if a very large object is created and destroyed over and over, the System.totalMemory might max out faster, and the GC will be called sooner and more frequently.
I'll look into this further. Thanks for the link, mooska! :)
EDIT: This is a dumb idea, for reasons posted below. :D Clearly this is a way to force the GC, but there's really no practical benefit for using it. What we want, instead, is a system that estimates the amount of active memory in use by Flash, and, if possible, helps the programmer find a memory leak if there is one.
#If you have any other info about this subject , Please add it free.# |

