2
votes

I have an AIR app that renders images from a Flash editor. You can customize several surfaces - they all have the same width and height. Each surface is then rendered by the AIR app.

I'm struggling with a memory leak I fail to address.

For each surface I need to render, I have a Sprite containing many components (other sprites - some having event listeners, BitmapDatas and other children components, sprites, etc).

I'm already aware of BitmapData issues to be garbage collected, I tested both :

  • creating a new BitmapData for each rendering then dispose() and point to null
  • reuse a single BitmapData for each rendering

Memory leak is still happening in equal proportion.

Here is my loop :

var bm:BitmapData = new BitmapData(destDim.x, destDim.y, true, bgColor);
var mtx:Matrix = new Matrix();
trace('before drawing :'+(System.privateMemory/1024));
bm.draw(myBigSprite, mtx, null, null, null, true);
trace('after drawing :'+(System.privateMemory/1024));
var result:Bitmap = new Bitmap(bm, PixelSnapping.NEVER, true);

//return result and encode Bitmap to png

result.bitmapData.dispose();
result.bitmapData = null;
result = null;

Result :

before drawing :208364
after drawing :302816
Wrote bitmap to file: surface0.png
before drawing :303296 
after drawing :446160 
Wrote bitmap to file: surface1.png 
before drawing :446160
after drawing :565212
Wrote bitmap to file: surface2.png 
before drawing :565924
after drawing :703100 
Wrote bitmap to file: surface3.png 
before drawing :703572
after drawing :834420 
Wrote bitmap to file: surface4.png 

I feel like I'm missing something in draw function behaviour. It seems like I have newly created instances of the components of myBigSprite that persists after draw operation.

I tried to completely destroy myBigSprite at the end of each loop, it does not change anything....

Any hint would be appreciated !

2
how do you know the garbage collection process ran? do you force it? also just as a sidenote, forcing gc by calling System.gc() only works in debug player as far as i know.Patang
As you can see the memory between the end of a draw operation and the beginning of the next one is almost equivalent, but there is a lot of memory consuming operations in between. Especially encoding bitmap to png. So I assume GC process is doing its job as all the resource I wanted to be cleaned are cleaned except for the draw function. I never explicitly call System.gc().Bedu33
do you set your "result" to null, since it holds a reference to the bitmapdata?Patang
Yes I do, I edited my snippet as it was not clear, sorry.Bedu33
@Bedu33 Normally, Garbage Collector is lazy and will not dispose of any unused memory as soon as it is unused. So I advise you to try calling System.gc() explicitly and see the memory measurements. If doing so will resolve your problem, then it is not a memory leak, it is just GC being lazy.Organis

2 Answers

1
votes

Ok guys, I eventually understood and fixed this issue.

First of all, I installed and ran Adobe Scout. Excellent tool.

Adobe Scout memory profiling

As you may not see (plus it's in French language), I generated 3 surfaces corresponding to the edges. The "big" green bar on the right which is mass memory consuming represent "Bitmap display objects". Interesting ! Never heard of those before.

A Google search later, I found this article : https://help.adobe.com/en_US/as3/dev/WS5b3ccc516d4fbf351e63e3d118a9b90204-7e26.html

It explains

For example, in the code excerpt shown earlier, once the load operation for the pict Loader object is complete, the pict object will have one child display object, which is the bitmap, loaded. To access this bitmap display object, you can write pict.getChildAt(0).

So I began to undestand that, somehow, maybe Bitmap object are attached as children on some objects of myBigSprite.

Finally, I created a recursive function to search and destroy all Bitmap, BitmapData and ByteArray objects contained in myBigSprite AFTER the draw operation

//inside render function
bm.draw(myBigSprite, mtx, null, null, null, true);
destroyDisplayObjects(myBigSprite);

...

private function destroyDisplayObjects(obj):void{
    if ("numChildren" in obj){
        for (var i:int = 0; i<obj.numChildren; i++)
        {
            destroyDisplayObjects(obj.getChildAt(i));
        }
    }
    else {
        if (flash.utils.getQualifiedClassName(obj) == "flash.display::Bitmap"){
            //trace ('FREE BITMAP');
            obj.bitmapData.dispose();
            obj.bitmapData = null;
            obj = null;
            return;
        }
        else if (flash.utils.getQualifiedClassName(obj) == "flash.display::BitmapData"){
            //trace ('FREE BITMAPDATA');
            obj.dispose();
            obj = null;
            return;
        }
        else if (flash.utils.getQualifiedClassName(obj) == "flash.display::ByteArray"){
            //trace ('FREE BYTEARRAY');
            obj.clear();
            obj = null;
            return;
        }

        return;
    }
}

Et voilà, memory is 100% cleaned after the draw operation, no more leak :)

0
votes

You should declare you Bitmap and BitmapData outside of any functions and then simply recycle them for usage inside of your loop (instead of creating a new anything to be added in memory).

Use .dispose() only on the last image when you're sure you don't need bitmap data from bm variable anymore. Otherwse, if disposed, you'll have to create a new alternative var someThing :BitmapData = new BitmapData again for further usage.

////# declare globally (not inside some specific function..)

//var destDim :Point = new Point(your_X_num , your_Y_num);
//var bgColor :uint = 0x505050;

var bm:BitmapData = new BitmapData(destDim.x, destDim.y, true, bgColor);
var result:Bitmap = new Bitmap(bm, PixelSnapping.NEVER, true);
//result.bitmapData = bm; //can be set here but done within function for clarity...

var mtx:Matrix = new Matrix();


////# update bitmap by replacing its bitmapdata with new colour values of existing pixels

function Here_is_my_loop (): void 
{
    trace('before drawing :'+(System.privateMemory/1024));

    //overwrite pixel colours in bitmap (result)
    bm.draw(myBigSprite, mtx, null, null, null, true);
    result.bitmapData = bm; //update bitmap

    trace('after drawing :'+(System.privateMemory/1024));


    //return result and encode Bitmap to png

    //result.bitmapData.dispose();
    //result.bitmapData = null;
    //result = null;
}