Has anyone figured out how AS3 actually handles garbage collection? I'm having so many issues releasing memory in a game I'm developing.
Made a small demo:
public class MemoryTesting extends Sprite
{
static protected var list:Array = null;
public function onKeyDown(event:KeyboardEvent):void {
if( event.keyCode == 65 ){ //A key - adds memory
if( list == null ){
list = [];
for( var index:int = 0; index < 10000000; ++index ){
list.push(new Matrix3D());
}
}
}
else{ //Any other key removes memory.
if( list ){
var size:int = list.length;
for( var index:int = 0; index < size; ++index ){
list.pop();
}
list.length = 0;
list = null;
}
System.gc();
}
}
}
Running Flash Player Debugger stand-alone 11.4r402 in Windows 7. Watching the Task Manager, with no keys pressed, the debugger sits idle at 11,000 K.
Pressing a (adding 10Mil Matrix3D classes) takes it up to 962,000 K.
Pressing another key (removing references to the Matrices and nulling the array) depends on how many times I press it.
The first time we call GC - drops to 255,000 K.
The second GC call - 92,000 K.
Third - 52,000 K.
Forth - 42,000 K.
Fifth - 39,000 K.
Sixth & any consecutive times after sits at 38,000 K.
I hear people talking about the GC waiting for "opportune times" to collect. But this is an empty application, not even a enter_frame event and there is no amount of time you can leave it idle for it to remove the remaining 27,000 K (38,000 - 11,000).
Sitting at the new low, if we re-add the matrices we move back up to 975,000 K.
That is to say, 13,000 K more than the first time. If I repeat this add/remove, it stays the same, going back up to 975,000 K and down to 38,000 K.
Remember, there is nothing going on in this application. My actual application has 650mb of raw bitmap data, let alone 100mb of SWF to parse and 500mb of XML classes that I only use in initialisation code.
I've read multiple times that even calling GC manually is bad, let alone 6 times. But none of the Matrix3D's will be released if I don't.
How does anyone handle this? Shall I just call GC 6 times at the end of initialisation?
Edit:
I was testing in release mode for differences and whether, without the System.gc() call, that if it doesn't free the memory from flash, at the least re-uses it properly. It does eventually, but with a new, higher footprint. With a full list sits at 990,000 K, clearing it takes it to 1,050,000 K.
This is for data that initially cost us 962,000 K RAM. That's 90MB of weird internal flash GC memory. Let alone ignoring that it won't ever give the memory back to the OS (without the explicit GC calls).
Actionscript's GC is weird, nothing to say,
If you'll try to use something like this, it helps (I just tested and GC clears out the memory on the very first try (key click)), Just changed Array to Vector to test more quicker, just the same should happen with Array too. My enviroment is FlashCC in this case.
package
{
import flash.display.Sprite;
import flash.events.KeyboardEvent;
import flash.geom.Matrix3D;
import flash.net.LocalConnection;
import flash.system.System;
import flash.utils.setTimeout;
public class MemoryTesting extends Sprite
{
var list:Vector.<Matrix3D> = null;
function MemoryTesting()
{
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}
public function onKeyDown(event:KeyboardEvent):void
{
var matrx:Matrix3D;
var index:int
if (event.keyCode == 13)
{
trace(System.totalMemory, "starting to fill...")
if (list == null)
{
list = new Vector.<Matrix3D>
for (index = 0; index < 1000000; ++index)
{
matrx = new Matrix3D();
list.push(matrx);
}
}
trace(System.totalMemory, " done...")
}
else
{
if (list)
{
trace(System.totalMemory, " preparing to delete...")
list.splice(0, list.length);
list = null;
}
//force GC to work normally, it really helps (at least in my case)
try
{
new LocalConnection().connect('foo');
new LocalConnection().connect('foo');
}
catch (e:*)
{
}
setTimeout(function()
{
trace(System.totalMemory, " deleted")
}, 50)
}
}
}
}
This strange snippet really helps on most cases
try {
new LocalConnection().connect('foo');
new LocalConnection().connect('foo');
} catch (e:*) {}
Here is the freat article:
http://gskinner.com/blog/archives/2006/08/as3_resource_ma_2.html
Related
My app is very slow on mobile devices.
It uses a lot of Event.ENTER_FRAME event listeners, so I switched (as I read they are much more performance heavy) to a global timer that these classes listen for if they need to update (they only use these timers if they have been initiated by a TouchEvent).
I also tried enabling hardware acceleration, and switching to using the CPU, but these have not helped with the lag issues to a noticable amount. (my app has very few animations so I think this is the best decision)
I do have a lot of MovieClips in it, however very few of them actually animate, so I doubt this is an issue. (This is also why I do not freeze and unfreeze objects, as they are never removed from the stage)
On this website they said the following about timers
– the more timer instances linger in the system, the more you can expect slow and choppy gameplay or unresponsive UI.
I only use one timer, so I didn't think this would be an issue, but I do have a lot of event listeners that use this timer to update, so I can only figure that the timer must be the issue. Thus to avoid lag I am looking for a better alternative.
To be clear, I'm not using many Event.ENTER_FRAME event listeners anymore, I've mostly switched to timers. With that being said, here is an example of a more resource demanding one.
package
{
import flash.display.MovieClip;
import flash.events.Event;
public class mc_BattleScene extends MovieClip
{
public static var mc_PlayerFace:MovieClip;
public static var enemyAttackEffect:MovieClip;
public static var mc_playerHitBox:MovieClip;
public static var battler1:Number = 1;
public static var battler2:Number = 1;
public static var battler3:Number = 1;
public var lvlModifier:Number;
public var dmgReduction:Number;
public var dmgDealt:Number;
public static var dmgSpeed:Number = 1;
public function mc_BattleScene()
{
visible = false;
addEventListener(Event.ENTER_FRAME, onFrame);
mc_PlayerFace = playerFace_mc;
enemyAttackEffect = attackEffect_mc;
mc_playerHitBox = playerHitBox_mc;
function onFrame(e:Event):void
{
battler1_mc.gotoAndStop(battler1);
battler2_mc.gotoAndStop(battler2);
battler3_mc.gotoAndStop(battler3);
if (Game.playerInteractionStatus[1])
{
//we are fighting
visible = true;
if (attackEffect_mc.hitTestObject(playerHitBox_mc))
{
// attack hit us, deal dmg
dmgReduction = (Game.playerStats[2] * (Game.enemyStats[1]));
dmgDealt = Game.enemyStats[1] - dmgReduction;
attackEffect_mc.x = 516;
if (Game.playerStats[0] - Math.round(dmgDealt) <= 0)
{
// round dmg to 0 (were dead)
Game.playerStats[0] = 0;
}
else
{
// deal damage to us
Game.playerStats[0] -= Math.round(dmgDealt);
}
}
else if(attackEffect_mc.hitTestObject(wall))
{
//stop the player from moving (by doing nothing)
}
else
{
attackEffect_mc.x -= dmgSpeed;
}
}
else
{
// reset the position of the attack effect if we are not fighting
visible = false;
attackEffect_mc.x = 516;
}
}
}
}
}
This example of Event.ENTER_FRAME is one of the few that actually are this complicated, the other Event.ENTER_FRAMEs tend to simply update values (ex: update a text field to display correct number variables)
For example: creating multiple vars inside an enterframe could clog
the memory and cause lag.
I've avoided doing stuff like this, as not only what you've said, but in my opinion I think it doesn't make sense to continuously define it in a function, I update these values only.
Resources
http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-8000.html
max links allowed
This current code when it is the upright state it runs the bass sound repeatedly until it starts distorting the sound and running poorly. How would i dispose of the sound when it has finished running and stopping it run multiple times as i would like the sound only to play once when it has gone into this upright state. Thanks alex.
public void determineState(){
accelX = Gdx.input.getAccelerometerX();
accelY = Gdx.input.getAccelerometerY();
accelZ = Gdx.input.getAccelerometerZ();
state = "NULL";
if(accelX < 1 && accelX > -1){
if(accelY > -1 && accelY < 1){
if(accelZ > 9 && accelZ < 11){
state = "UPRIGHT";
}
}
}
}
public void playSound(String soundString){
System.out.println(soundString);
if(soundString != "null"){
sound = Gdx.audio.newSound(Gdx.files.internal(soundString + ".wav"));
long id = sound.play();
sound.setVolume(id, 3);
}else if (soundString == "null"){
}
}
You should not create a new sound and dispose it every time you want it to play. This is wasting time reloading it over and over. Store your loaded sounds in an array, or load them with an AssetManager and get the references from there.
Your playSound method would look something like this:
public void playSound (String soundFileName){
sound = assetManager.get(soundFileName, Sound.class);
sound.play();
}
By the way, it doesn't make sense to set volume to 3 since it's on a 0-1 scale.
If you want something to happen only one time when a state changes, than call that method when the state changes, not on every frame as it seems you are doing.
i.e. do this:
//...
if(accelZ > 9 && accelZ < 11){
state = "UPRIGHT";
playSound("uprightStateChange.wav");
}
So I have been using Haxe for a while and it has occurred to me recently that I don't really get what happens on some other the non-flash targets as far as memory cleanup. I mean 'new'ing everything and dumping it by setting references to null gives me this feeling that there are memory leakages, but I can't seem to find the documentation I'm looking for.
Specifically I use dictionaries/maps a decent amount. Like this:
var items:Map<String, MyObject> = new Map();
items.set("someKey", new MyObject(args...));
// Later
items["someKey"].doSomething();
items["someKey"].setVal(2);
...
// When Finished
items.remove("someKey");
The last line there just dumps my object somewhere into oblivion and hopefully gets garbage collected (at least on the Flash target).
I put together a little program just to see the cleanup in action on Flash/Neko and then change it for other targets, but I am failing to even see the cleanup on the Flash Neko target. Here is the project code:
package;
import openfl.display.Sprite;
import openfl.events.Event;
import haxe.ds.StringMap;
import openfl.events.KeyboardEvent;
import openfl.ui.Keyboard;
class Main extends Sprite
{
private var keypressID:Int;
private var itemID:Int;
private var dict:StringMap<Sprite>; // Using this since I read Map<String, T> just compiles to StringMap<T>.
public function new()
{
super();
addEventListener(Event.ENTER_FRAME, init);
}
private function init(event:Dynamic):Void
{
removeEventListener(Event.ENTER_FRAME, init);
// Entry point.
keypressID = 0;
itemID = 0;
dict = new StringMap();
stage.addEventListener(KeyboardEvent.KEY_UP, keyPress);
}
private function keyPress(event:Dynamic):Void
{
if (Std.is(event, KeyboardEvent) && cast(event, KeyboardEvent).keyCode == Keyboard.A)
{
trace('ID: $keypressID - Adding Item');
keypressID += 1;
for (i in 0...10000)
{
itemID += 1;
dict.set('$itemID', new Sprite());
}
}
else if (Std.is(event, KeyboardEvent) && cast(event, KeyboardEvent).keyCode == Keyboard.R)
{
trace('ID: $keypressID - Removing Items');
keypressID += 1;
removeItems();
}
// Force garbage collector to run.
else if (Std.is(event, KeyboardEvent) && cast(event, KeyboardEvent).keyCode == Keyboard.C)
{
trace('ID: $keypressID > Starting GC');
keypressID += 1;
forceGarbageCollection();
}
}
private function removeItems()
{
trace('ID: $keypressID > Remove All Item');
for (val in dict.keys())
{
dict.remove(val);
}
dict = new StringMap();
}
private function forceGarbageCollection():Void
{
neko.vm.Gc.run(true); // This does not work.
}
}
I run this on Windows and under task manager, my neko process only grows and never shrinks. Its gets up to 500MB quick when hitting 'A'. I then 'R' to remove all references to the items, but they never get collected it seems even when I force the GC.
I also tried storing openfl.util.Timer objects with event listeners attached to them to do traces and they never seem to get collected either. They just keep tracing. Now I suspect that may be because of the event listener reference, but am sure I have seen that trick in other AS3 memory leak tracking code.
Am I missing something or doing something wrong?
Edit:
I have modified the above question to reflect this, but I was mistaken about Flash player. I did get the memory to be reclaimed when running in the Flash player using flash.system.System.gc(); It seems the problem may be specific to neko which my question still addressed.
So I have been making this game with Action Script 3, and CS5.5. What you are trying to do is avoid asteroids while you fly through space. I thought it would be cool to have the planets in out solar system be moving down the screen throughout the game in the background. Kinda to make it look like you were flying pass them. The way I did this was I added five to their y coordinate each frame per second. Once their y coordinate reached 600 ( the bottom of the screen ) I would add a new planet which would do the same thing. For some reason once I got to Saturn everything got weird. Saturn came to early, and so did Uranus. I had no idea what was going on. I have been frustrated with this for a good hour. Here is the part where I think there is the problem.
public function onTick(timerEvent:TimerEvent):void
{
earth.PlanetMovement(5);
if (earth.y==600)
{
mars.PlanetsStart(300, -100);
addChild( mars );
levels=levels+5;
}
mars.PlanetMovement(5);
if (mars.y==600)
{
jupiter.PlanetsStart(300,-150);
addChild (jupiter);
levels=levels+10;
}
jupiter.PlanetMovement(5);
if (jupiter.y==600)
{
saturn.PlanetsStart(300,-155);
addChild (saturn);
levels=levels+20;
}
saturn.PlanetMovement(5);
if (saturn.y==600)
{
uranus.PlanetsStart(300,-160)
addChild ( uranus);
levels=levels+25;
}
uranus.PlanetMovement(5);
PlanetMovement, and PlanetsStart are two functions in the Planets class. If you need more info please tell me.
EDIT: I guess I should explain further. PlanetsStart is a function that has the starting coordinate of each movieclip. So once earth reached a y coordinate of 600, then mars starts at (300, -100). Then it is added to the screen. levels is a variable that raises the score each fps. PlanetMovement is how much each movieclip will move each fps. If I were to use >= then the score would raise too much.
This is exactly what happens. earth shows up where it is supposed to. Then mars showd up on time. Then for some reason Saturn pops up in the middle of mars, and Jupiter. After this Saturn reaches the bottom making Uranus appear. Then Jupiter reaches the bottom, and everything works as it should. Saturn shows up, and then Uranus in order
Ugh. Unfortunately, this is more complex than you are implying. It would really help you to write a code plan of some sort first, develop some psuedo-code that will show basic logic (and the logic errors), and then code after that.
Below is an example of a better way to structure this idea. However, I don't think your idea is terrible memory conscious. You should only bring in the planets as needed, and remove them as needed as well. Look into a technique called "object pooling" to help better structure this.
Solar system class:
package
{
import flash.display.MovieClip;
import flash.events.Event;
import flash.geom.Point;
public class SolarSystem extends MovieClip
{
private var PLANET_NAMES:Array = new Array("earth", "mars", "jupiter", "saturn", "uranus");
// you will have to fix these values:
private var PLANET_STARTS:Array = new Array(new Point(300, -100), new Point(300, -100),new Point(300, -100),new Point(300, -100),new Point(300, -100));
private var planets:Array;
private var maxY:Number = 600;
private var speed:Number = 5;
private var levels:Number = 0;
private var levelIncrement:Number = 5;
public function SolarSystem()
{
super();
addEventListener(Event.ADDED_TO_STAGE, initHnd, false, 0, true);
}
private function initHnd(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, initHnd);
runPlanets();
addEventListener(Event.ENTER_FRAME, frameHnd, false, 0, true);
}
private function runPlanets():void
{
for (var i:int = 0; i < PLANET_NAMES.length; i++)
{
planets[i] = new Planet();
planets[i].name = PLANET_NAMES[i];
Planet(planets[i]).startPlanet(PLANET_STARTS[i]);
this.addChild(planets[i]);
}
}
private function frameHnd(e:Event):void
{
if(planets && planets.length > 0){
// move all the planets until they are too low, then remove them.
// decrementing loop because planets will be being removed occasionally.
for (var i:int = planets.length -1; i >= 0; i--)
{
if (planets[i] && Planet(planets[i]).y >= maxY) {
// this seems very strange to me, but it will replicate what your code says:
levels += (levels + levelIncrement);
try {
removeChild(Planet(planets[i]));
planets[i] = null;
}catch (e:Error) { }
} else if ( Planet(planets[i]).isMoving ){
Planet(planets[i]).movePlanet(0, speed);
}
}
} else {
removeEventListener(Event.ENTER_FRAME, frameHnd);
}
}
}
}
and here is the planet class:
package
{
import flash.display.MovieClip;
public class Planet extends MovieClip
{
private var _isMoving:Boolean = false;
public function Planet()
{
super();
}
public function startPlanet(sx:Number, sy:Object):void
{
this.x = sx;
this.y = sy;
isMoving = true;
}
public function movePlanet(dx:Number, dy:Number):void
{
if (isMoving)
{
this.x += dx;
this.y += dy;
}
}
public function get isMoving():Boolean
{
return _isMoving;
}
public function set isMoving(value:Boolean):void
{
_isMoving = value;
}
}
}
Again, this is not the best way to do this, but it is more manageable to group concepts and actions into classes.
HTH.
Right off the bat I can see a major issue. Change all the statements from:
if (saturn.y==600)
to
if (saturn.y>=600)
Just saying == means that you're only going to trigger the conditional in the case that your Y value is exactly 600.
Note you can also change stuff like levels = levels+20 to simply levels += 20;.
When moving stuff around like this it is a good idea to check the position by using ">=" instead of "==". What if your object starts at y=1? Then it will never reach 600 it will just keep on going down forever.
Also you seem to be calling PlanentMovement even when PlanetsStart hasn't been called yet.
I've been teaching myself actionscript 3 over the past month or so and recently ran into an issue where an object kept doing things after I thought it had been removed. I figured out that the problem was caused by an event listener using the default of useWeakReference = false and I'm wondering why that's the default.
what is the advantage to not using a weak reference? and why is that the default?
it seems to me that in general you'd want to use weak references, so I must be missing something.
Thanks,
-Ted
the point is, that weak references are expensive ... they are both slower and consume more space ... here is some benchmark code:
package {
//{ region imports
import flash.display.Sprite;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.system.System;
import flash.utils.*;
//} endregion
public class Main extends Sprite {
public function Main():void {
switch (0) {
case 0: this.benchmarkDispatchers(false); break;
case 1: this.benchmarkDispatchers(true); break;
case 2: this.benchmarkDictionaries(false); break;
case 3: this.benchmarkDictionaries(true); break;
}
}
private function benchmarkDictionaries(weakKeys:Boolean, size:uint = 1000000):void {
var a:Array = [];
for (var i:int = 0; i < size; i++)
a.push( { "foo":i } );
var d:Dictionary = new Dictionary(weakKeys);
var start:int = getTimer();
var mem0:int = System.totalMemory;
for (var j:int = 0; j < size; j++)
d[a[j]] = j;
trace("adding "+size+" keys took "+(getTimer()-start)+" msecs and "+(System.totalMemory-mem0)+" B of memory with weakKeys == "+weakKeys);
}
private function benchmarkDispatchers(weakRef:Boolean, size:uint = 100000):void {
var a:Array = [];
var f:Function = function (i:*):Function {
return function ():void { i; }
}
for (var i:int = 0; i < size; i++)
a.push( f(i) );
var e:EventDispatcher = new EventDispatcher();
var start:int = getTimer();
var mem0:uint = System.totalMemory;
for (var j:int = 0; j < size; j++)
e.addEventListener("foo", a[j], false, 0, weakRef);
trace("adding " + size + " event handlers took " + (getTimer() - start) + " msecs and " + (System.totalMemory - mem0) + " B of memory with weakKeys == " + weakRef);
}
}
}
this is, what i get on my machine:
adding 100000 event handlers took 679 msecs and 6922240 B of memory with weakKeys == false
adding 100000 event handlers took 1348 msecs and 13606912 B of memory with weakKeys == true
adding 1000000 keys took 283 msecs and 16781312 B of memory with weakKeys == false
adding 1000000 keys took 906 msecs and 42164224 B of memory with weakKeys == true
results are a little more drastic for dictionaries, most probably because there are no ActionScript calls involved, concerning time, and since some storage overhead in event handler registering lessens the difference between memory needed (as you can see, it's 69 Byte/handler and 16 Byte/key, when comparing weak references) ...
so yeah, it is about performance ... using weak references is not about the cool fact, that you don't have to remove the listener in order for an object to die ... if you want to have a scalable app, you need to do these kind of things yourself, and if you want it to be 100% reliable, you can't hope for the GC to do your job, but you need tomake cleanups yourself ... and also, if you have a good hierarchy in your app, you will probably not run into this problem much to often ... in a sense, it is a luxury, if you don't wanna spend you time doing proper object cleanups, because problems that cannot be solved without weak references are rare ... it should be used when it offers a real advantage, not just out of laziness ... i think that is why it is false by default ...
hope that helps ... ;)
greetz
back2dos
You're right, typically you do want to use weak references, probably about 99% of the time. I think the default value really should be set to true, but we just have to deal with it not being so. I suggest that you get in the habit of always using weak references, and you will save a few headaches, and see some performance benefits as it helps with garbage collection management.
So when would you want it to be false? Basically any time you don't want things automatically clean up when the garbage collector decides you're not using it anymore. These situations are rare, and when the time comes you'll probably just know.
HOWEVER That might not solve your particular problem on it's own. The garbage collector only runs at unpredictable intervals, so even though you remove all references to an object, it might still handle these events until it is cleaned up. Your best bet is to remove the event listeners yourself to guarantee they will not be triggered again. You can do that with the removeEventListener method.
Good luck,
Tyler.
Weak references can bite you when doing asynchronous operations, since anything that is not executing for tethered to the object graph can be garbage collected at any moment.
Particularly, never use weak references for event handlers that are scoped functions:
function weakRefSample() : void
{
var evntDispatcher : IEventDispatcher = new EventDispatcher();
evntDispatcher.addEventListener(Event.COMPLETE, scopeHandler, false, 0, true);
function scopedHandler(event : Event) : void
{
}
}
There are two things that go wrong in the above example:
scopeHandler can be collected because there it is not tethered to the object graph
the execution scope of weakRefSample can be collected because nothing requires it to exist (nothing is using scopedHandler), so evntDispatcher can be collected. If evntDispatcher was waiting on another asynchronous event, it might be collected before that event completes.
I have found that Timer instances which have weak referenced event listeners sometimes do not fire the event.