This question already has answers here:
disable GC in AS3
(2 answers)
Closed 9 years ago.
so, I am making really enormous project with as3, and I`d like to know how to disable garbage collector, as user might be on for long and I dont want him or her to just be cut off because garbage collector removed some listener somewhere that should be now needed.
is there easier way to disable garbage collector than go to every single listener and add "false, 0, true" extension after naming the listener?
No it's not possible, but what you're afraid of isn't really an issue.
Also, making event listeners weak doesn't disable the garbage collector, in fact it makes whatever object they are set to eligible for garbage collection if that is the only reference left to them.
That is possible actually, but it needs discipline...
When an as3 object looses all its references to others, it becomes a candidate for garbage collection. To avoid this you can use the following method:
When you need an object to persist in memory, bind it to somewhere accessible.
Here I supplied an example code.
First create the class below:
package your.package.path;
public class noGc {
protected static var vault:Array = [];
// In case we forget that noGc should stay static....
public function noGc(){throw new Exception('Error: Instance from STATIC noGc');}
public static function hold(o: *): void {
if(vault.indexOf(o)==-1)vault.push(o); // no multiple references in vault
}
public static function release(o: *): Boolean {
var i: int = vault.indexOf(o);
if(i == -1)return(false); // return value is for information only
vault.splice(i,1); // remove object from vault
return(true);
}
public static function releaseAll(): void {
vault.length = 0;
}
} // end class noGc
To avoid gc on "yourObject"
noGc.hold(yourObject);
To allow gc on "yourObject" back
noGc.release(yourObject);
For normal code flow start holding the objects right after creation of them.
Then you should release them at the end of their use.
Also you have to keep an eye on exceptions since exceptions break the normal flow you should handle them and release the objects becoming irrelevant after the exception.
Forgetting an object held means use of unnecessary memory, a.k.a. a memory leak.
As I told before, It needs discipline.
Finally, when you need to wipe all objects that are held use,
noGc.releaseAll();
Hope this helps.
Related
Are there any general guide lines for using retain and release for objects in cocos2d-X ? When creating objects in a function, is it true that the functions memory is cleaned up the second the function returns. When a object is created, calling the retain function of the object, will retain object beyond the function return ?
Kind Regards
Generally in c++ you have this behaviour:
void foo() {
Object a;
Object *pA = new Object();
(…)
}
This would result in a being destroyed automatically at function end, as it was allocated on stack. The *pA would not get destroyed, as it was allocated on the heap (thus, you only loose the reference to it, but the object itself still lives).
Cocos implements a thing called "Automatic Reference Counting" : each CCObject has a reference counter and two methods retain() and release(). The way this works is, that every time you create an object, it gets registered in cocos structers (CCPoolManager). Then with every frame (between them being drawn) there is a maintenance loop which checks the reference counter of all objects : if it is 0 this means (to cocos) that no other objects reference it, so it is safe to delete it. The retain count of an object is automatically incresead when you use this object as an argument for an addChild function.
Example :
void cocosFoo() {
CCSprite *a = CCSprite::create(…);
CCSprite *b = CCSprite::create(…);
this->addChild(b);
}
What happens here is this :
Two CCSprites are created, cocos knows about them.
The b sprite is added to this object (say a CCLayer)
The function ends, no objects are destroyed (both of them being on heap).
Somewhere between this and next frame, the maintanance gets run. Cocos chcecks both sprites and sees that a has reference count == 0, so it deletes it.
This system is quite good, as you don't need to worry about memory management. If you want to create a CCSprite (for example), but not add it as a child yet, you can call retain() on it, which will raise its reference counter, saving it from automatic deletion. But then you'd have to remember about calling release() on it (for example, when adding it as a child).
The general things you have to remeber about are :
Each call to retain() by you needs to be paired with release().
You generally shouldn't delete CCObjects yourself. If you feel that you need to, there is a conveniece macro : CC_SAFE_DELETE(object)
So to answer your questions in short :
Are there any general guide lines for using retain and release for objects in cocos2d-X ?
Yes, you should generally not need to do it.
When creating objects in a function, is it true that the functions memory is cleaned up the second the function returns.
Answer to this is the whole text above.
When a object is created, calling the retain function of the object, will retain object beyond the function return ?
Yes, as will adding it as a child to another (retained in any way) object.
Here is the thing,
cocos2dx has an autorelease pool which drains the objects which have retain count=0 which is a variable to keep in check the scope of the cocos2dx object.
Now when you create new object using the create method it is already added to the autorelease pool and you don't need to release it or delete it anywhere , its like garbage collector in java, takes care of garbage objects behind your back.
But when you create new object using 'new' you definitely need to release it in its destructor or after its use is over.
Second thing,
when your object is added to the autorelease pool but you need it somewhere else you could just retain it , this increments its retain count by one and then you have to manually release it after its use is over.
Third Thing,
Whenever you add child your object it is retained automatically but you don't need to release it rather you remove it from the parent.
Given an interface like
%feature("director") HeldBase;
%feature("smartptr") HeldBasePtr;
typedef SmartPtr<HeldBase> HeldBasePtr; // a minor wrapper around boost::shared_ptr
// Various typemaps that ensure Java-side HeldBase instances are always HeldBasePtr
class HeldBase {
public:
virtual void doSomething(int) = 0;
}
class Holder {
void hold(HeldBasePtr hb);
void release(HeldBasePtr hb);
void clear();
void process(int seconds);
}
Now some code implements the HeldBase, on the java side doing something like:
class MyHeldBase: extends HeldBase {
void doSomething(int i) { System.out.println("Hello whirled"); }
}
Holder h = new Holder();
HeldBase local = new MyHeldBase();
h.hold(new MyHeldBase());
h.hold(local);
h.process(1000000); // presumably doing something with the held things.
The underlying C++ layer deals with the smart pointers "properly", so (for example) destroying the Holder (swig proxy and underlying C++ object) correctly decrements the ref counts on the underlying smart pointers.
Right now the 'local' held base works fine, but the one without a reference held on the java-side gives a "null upcall" error presumably after some gc causes the java instance to go away.
I noticed the generated swigTakeOwnership and swigReleaseOwnership methods, and tracked them down in the code to be toggle whether a GlobalRef or GlobalWeakRef is held by the director. So I did some wrapper code so that calls to hold(HeldBase hb) called hb.swigReleaseOwnership() and release() called hb.swigTakeOwnership(). Looking at the code it seemed that swigReleaseOwnership would convert the underlying director ref to a GlobalRef (so that the C++ layer would hold ownership of the Java MyHeldBase). And vice-versa for swigTakeOwnership.
Unfortunately this didn't work for unrelated reasons... something in our typemaps have rendered the swigTakeOwnership and swigReleaseOwnership uncallable, because they (think they) hold HeldBase * not HeldBasePtr *.
If that is the right way to solve this problem, I'll try to track down why those methods are not being generated correctly. But I still feel like there are unresolved issues (that are hard to think about.)
For example, what about:
HeldBase local = new MyHeldBase()
Holder h1 = new Holder(); Holder h2 = new Holder();
h1.hold(local);
h2.hold(local);
h1.release(local)
local = null;
In this case, the call to swigReleaseOwnership() happens twice, but the second is a noop. When release() call swigTakeOwnership (making the reference back into a GlobalWeakRef) the java layer is once again "owning" and h2 will get a null upcall. Also, if calls to swigTakeOwnership ARE required, then Holder.clear() seems to mean I need to add code to hold all the passed java-extended directors (at which point I can just do that to manage their lifecycle).
It "feels" like when using smart pointers with directors, the C++ side of the director should always just use a GlobalRef and things will "just work" but OTOH it seems that is a memory leak there since then the C++ side will keep the Java proxy alive and vice-versa.
I created this simple example
because I was using a more complex class, a menu item that I wanted to initialise all the settings in the Main class and then add it in in the Game class (and updating it) when needed (both classes are separate)
Class: Main (document class, is (ideally) where everything is initialised/created)
public class Main extends MovieClip
{
//testing passing on reference to Game
private var testBitmap:Bitmap;
private var testBitmapData:BitmapData;
private var testArray:Array;
public function Main():void
{
testBitmapData = new BitmapData(256, 256, false, 0xCCDDEE);
testBitmap = new Bitmap(testBitmapData);
testArray = [];
testArray.push(testBitmap); //Array for reference
game = new Game(540, 960, testArray);
//create Game class, pass reference to Object
game.x = 0;
game.y = 0;
}
}
Class: Game (created by document class, is (ideally) where everything runs)
public class Game extends MovieClip
{
private var testingArray:Array
public function Game(gameWidth:int, gameHeight:int, testArray:Array):void
{
this.testingArray = testArray; //assign to local Array and access
addChild(testingArray[0]);
//addChild to Game from an item intialised in Main, doesn't work >___<
}
}
.
.
.
the thing is, in my original Game class; it receives an initial bundle of cached BitmapData and a list Array that tells it which BitmapData it needs to cycle through
cut-down here (and that reference only for updating works (if I addedChild in Main already):
public function Game(gameWidth:int, gameHeight:int, cachedBitmapClipArray:Array)
{
this.cachedBitmapClipArray = cachedBitmapClipArray;
private function update():void
{
for each (tempCachedBitmapClip in cachedBitmapClipArray)
{
tempCachedBitmapClip.updateCurrentTile();
//but updating through the reference to an item initialised in Main works !!
}
}
}
.
how do I make the reference and passed in objects (or have access to) behave as in the original instance ?? (being able to addChild)
i.e. can objects cross 'scopes' (??) or should objects only be controlled (instantiated) in the class where they have been initialised
Well to answer the last question, yes objects can be passed from one object to another. I'm having a hard time understanding what exactly the problem is though. In generic programming terms Object or any other complex type are passed by reference, primitive types are also passed by reference but the internals of the AVM handle them in such a way as to treat them as passed by value. For a pretty clear (in my eyes at least) explanation of how arguments are passed via function/method calls, read here:
http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7f56.html
Check out other areas in the tree navigation on the left for more details on the language itself.
One thing to note though I think the compiler will work around this error, in the first bit of code you posted Game has a return value of :void for a constructor there should be no declared return type since it should be implicitly typed as whatever the class is.
To add some explanation in my own words regarding pass by reference or pass by value. Pass by value means that the value of the object, that is the bytes stored at the location pointed to by the variable are actually copied to a new memory location which is then used. What this means in practice is they are not the same bytes of memory but rather they are two separate memory locations each with the same value stored in them, modification of one value after the passing doesn't affect the original. Pass by reference is to say you pass the memory location therefore no new memory for the value is allocated, both values are actually pointing to the same memory location (the pointer to them itself may be different but that pointers both point to the same location in memory). In this case the objects are the same.
What you're doing is advisable, dividing the labor and enapsulating particular types of functionality in classes does make the code easier to work with. I think the only problem would be if the Game object itself is never added as a child to something in the display tree (something that is attached to the stage). It appears you're creating a new Game and setting it's position but never adding it as a child to the stage, then adding anything to Game is never a part of the display tree.
Can someone post an example of as3 code (specifically event listener included) that would be a simple example of something that could leak memory... also hopefully could you post a solution to the problem shown?
The question is: What is a simple example of leaking memory in an AS3 event listener and how can you solve it?
public class MySprite extends Sprite {
public function MySprite() {
if(stage) {
init();
} else {
addEventListener(Event.ADDED_TO_STAGE,init);
}
}
private function init(e:Event = null):void {
stage.addEventListener(Event.RESIZE,handleStageResize);
}
private function handleStageResize(e:Event):void {
// do some processing here.
}
}
Somewhere else:
var mySprite:MySprite = new MySprite();
someHolder.addChild(mySprite);
Now, if at some later point you remove mySprite, it'll still hang around in memory, because it has added itself (or a reference to itself) to the stage in the init() method.
In this scenario, the best way to avoid this could be removing the listener added to the stage when mySprite is removed from the display list.
private function init(e:Event = null):void {
addEventListener(Event.REMOVED_FROM_STAGE,cleanUp);
stage.addEventListener(Event.RESIZE,handleStageResize);
}
private function cleanUp(e:Event):void {
stage.removeEventListener(Event.RESIZE,handleStageResize);
}
I'm sure other people will tell you to use weak references when adding the listener to the stage, but you should remove your listeners anyway. If you don't, when you remove mySprite from the display list and have no other refs to it, will be eligible for GC and will eventually be wiped away from memory. But until that happens, the code in handleStageResize() will continue to execute.
I'll just follow up #Juan's answer - GC needs to be considered from the ground up as a critical aspect of application design. If you create an object, you must be aware of each reference to it, and remove each reference and nullify it to flag properly#. If you reference that object in array, that counts, if you reference it in a listener, that counts, if you reference it via a local variable, that counts too (though only during the life of the function), if its simply in the display list, that definitely counts, and on and on.
I go so far as to write my remove listener statements prior to adding them just to make sure.
I will almost always write a public destroy() method for any object to handle inner object hierarchies (parent calls destroy on child, which, in turn calls destroy on any children etc etc). Just removing / nulling a parent without doing so to each child is poor GC management.
And if you actually have any concerns that mem leak has sprung, trace out System.totalMemory just to make sure:
var mem:String = Number( System.totalMemory / 1024 / 1024 ).toFixed( 2 ) + ‘Mb’;
trace( mem ); // eg traces “24.94Mb”
Mostly - just be methodical about it - its not rocket science, but you have to be careful.
Cheers -
# and even if you do, flash makes up its own mind about when to actually do a sweep. The best we can to is ensure an object is properly flagged and trust that it will be dealt with efficiently.
I'm not going to post an example of this but I'll explain it a bit. There are 2 situations you are describing here.
memory leaks
processor overflows
AS3 handles memory and processor operations differently.
Memory Leaks happen when there are many objects created and destroyed. The objects leak memory when they have references and the object is destroyed with out destroying the references - therefor leaving a memory block of an unused object = leak.
Processor Overflows happen when you have many methods referencing each other with out 'closing the loop'.
I have a class which is called a number of times. When the application goes to the next stage these all have to be unloaded. Because of that, I created an unload() method in the class.
The problem is that I can't seem to set my uint variable "charId" to null in order to "unset" it. The "delete" command is not possible either as that is only applicable for dynamic variables or something in that kind of way.
Now I wonder, how am I supposed to unset this variable, so it's memory will be re-allocated later on?
The class's unload method:
public function unload():void
{
trace("Unloading character with charname '" + charName + "'.");
enterButton.removeEventListener(MouseEvent.CLICK, enterClicked);
removeChild(enterButton);
enterButton = null;
charName = null;
charId = null; //this is possible but not recommended - what's a better way?
lobbyInterface = null;
}
So yeah, it's practically possible as it changes the variable type - however it's not recommended and raising a warning. So, what's a better way to do it?
Note that this object is also unloaded in it's parent. Does that also free all these variables from memory?
uint, int, Number and Boolean are not nullable in AS3. Number can be NaN, but that is really the best you can get. int and uint are always just 32 bit, so you can't stuff a null-reference in there.
The type of cleanup you are trying to do cannot be accomplished since AS3 has the concept of sealed classes. A sealed class has a fixed size in memory. When it comes to instance variables, think of it as a C struct, you can only dump all of it, or nothing. You can do anything in C of course, it's a fixed block in memory, an entity of one reference per variable.
What you want to do is only work with dynamic variables, which are maintained differently.
You don't need to do this sort of cleanup since Flash has garbage collection like most runtimes nowadays. It also deals with nested and circular references, the only thing you have to be sure about is, that you delete any "outer" references to that class. Things that are generally not collected are objects on the display list, running timers and intervals, and I/O related stuff. As soon as you have a reference chain from there to your object, it will not be collected.
Let us say you have an object A with an event handler for a mouse movement on an object on some list, referencing an object B. B will not be collected, but as soon as there is no chain leading to an object, it will be collected (sooner or later, the GC is quite lazy. But the more memory you use, the more it does its work).