“Be inspired, enjoy your work, keep learning and never forget to play.” — gskinner

Memory Leak in AS3 Loader Class

Posted: October 22nd, 2007 | Author: Tiago Bilou | Filed under: Actionscript3 | 27 Comments »

Today I came across a nasty memory leak in the Loader Class.

I was writting a simple screensaver class that loads external images from the hdd and displays them. Sounds simple? It is. The problem was that everytime I loaded an image, the system memory would go up. I started by checking my eventListeners. They where all weak referenced. I went through the code looking for places where I could be keeping a reference and hence preventing the GC to collect it, but I couldn't find any.

There is something keeping the GC from removing the old images from memory...

Everytime the Loader calls the load() method the image is read into memory and stays there. I tried calling the unload() method but that doesn't help.

Then, how can one remove the loaded image from memory? If you know please let me know...

The only workaround I came up with was gskinners unsupported method to force the GC

This works great, but I would like to know what's keeping the GC from removing the previous images from memory...
Let me know if you have an answer.

Example showing the memory leak


27 Comments on “Memory Leak in AS3 Loader Class”

  1. 1 rob said at 1:24 am on November 3rd, 2007:

    Did you ever find a fix for this?

  2. 2 Tiago Bilou said at 6:51 am on November 3rd, 2007:

    I still couldn’t find a reason why the GB can not remove the previous images. However if you use gskinner’s unsupported method o force the GC it works

  3. 3 Daniel said at 5:11 pm on November 4th, 2007:

    I couldn’t understand some parts of this article Memory Leak in AS3 Loader Class, but I guess I just need to check some more resources regarding this, because it sounds interesting.

  4. 4 a_[w] said at 3:21 pm on November 8th, 2007:

    import flash.display.*;
    var loader:Loader = new Loader();
    ……
    var li:LoaderInfo = loader.contentLoaderInfo;
    if(li.childAllowsParent && li.content is Bitmap){
    (li.content as Bitmap).bitmapData.dispose(); // remove bitmap from memory
    }

  5. 5 Idoru said at 6:04 am on November 13th, 2007:

    To: a_[w]
    Well that works, but if you move to video objects you’ll find the same problem.
    Talking with Richard Galvan, from Adobe, he explained that this is a known bug that will be fixed soon when the the garbage collector is made to be more active.
    Also the localConnection work.around will cease to work then.

  6. 6 Phill said at 12:07 pm on November 17th, 2007:

    Are you sure it is a leak? It’s obviously eligible for collection (as the localconnection hack forces the collector to collect everything which doesn’t have a strong reference to it). Just because something is eligible doesn’t mean Flash will free the memory associated with it straight away. I’m not sure of the exact heuristics used but it may well not free that memory until it really needs it. I think if it was leaking the localconnection hack wouldn’t clear the memory as it wouldn’t think it was eligible.

  7. 7 Idoru said at 9:41 am on November 18th, 2007:

    Hi Phill,
    Well the problem is that the mark and sweep might not run for a very long period of time, and thus even if you need the extra memory you won’t have it, grant skinner described it quite accurately, and also as i said talking to adobe people they confirmed that the GC isn’t yet that optimized.

  8. 8 Ncatdesigner said at 3:30 am on November 26th, 2007:

    Hi everybody,

    I’m using a banner that loads various jpg, first a b&w jpg for a fast preview and then the swf or jpg version (only jpg for the moment) when I’ve loaded more than 20 images aprox. the web browser decrease the speed of the processor.

    Can I use the method above??

    I’ve used kind of
    import flash.display.*;
    var loader:Loader = new Loader();
    ……
    var li:LoaderInfo = loader.contentLoaderInfo;
    if(li.childAllowsParent && li.content is Bitmap){
    (li.content as Bitmap).bitmapData.dispose(); // remove bitmap from memory
    }

    not working…

    Advise! Newbie question:
    Why many codes use foo, what’s foo?

  9. 9 Tiago Bilou said at 8:55 am on November 27th, 2007:

    Hello Ncatdesigner

    foo is just a name coders like to use. Instead of foo you can use any name you want.

    As for the the code, if you want to use the hack to call the GC make sure you clean up any references (events, addChilds, …)
    Then just place the lines to force the GC to run inside your IF statement. That should do the trick.

  10. 10 Guillaume Lecanu said at 11:38 am on November 30th, 2007:

    Hi,

    I have detected the same problem with the Image() object.
    I have reported it : http://bugs.adobe.com/jira/browse/ASC-3030

    But no news, I think people need to vote for this bug ticket to permit to solve it more quickly.

  11. 11 Dreaming in Flash » Blog Archive » Another Way to Force the Garbage Collection said at 4:05 pm on December 20th, 2007:

    [...] version only). Previous to this method the only known way to force the garbage collection was the localConnection hack so this is good [...]

  12. 12 Dreaming in Flash » Blog Archive » Memory Leak Unit Test said at 9:57 am on January 7th, 2008:

    [...] our resources from memory, which makes it hard to test something like this. But there are two ways ([2] and [3]) to force the GC to run so we can use that to test that something that should have been [...]

  13. 13 Peter said at 4:28 pm on February 9th, 2008:

    Just on the foo question…
    In an old and famous WWII movie (can’t remember the title) some guys always use the expression FUBAR and you find out at the end it stands for F@cked Up Beyond All Recognition.
    That’s the origin of naming sample variables foo and bar.

  14. 14 0e28503c2640 said at 7:32 pm on May 10th, 2008:

    0e28503c2640

    0e28503c26406d129e13

  15. 15 Samuel Agesilas said at 11:04 am on May 14th, 2008:

    Hi Guys,

    I was googling some info for an AS3 concern and I came across this post. I understand everyone’s concern here but I can assure you guys that this is NOT a memory leak. Generally speaking memory leaks are caused when a program allocates memory but fails to de-allocated it correctly( The famed dealloc method in C/C++). AS3 developers for the most part can’t cause memory leaks directly because of the GC, that in essence is what the GC is for, to prevent these kinds of things(there are exceptions though but they are rare). If there is a memory leak in the VM (like the one cause by the XML object several years back) is mostly likely due to faulty code in the VM. Now having said this I can assure that in your case this is not a memory leak. You see the new VM uses a much more advanced and performant GC that uses the much safer and accurate mark and sweep method. Mark and sweep is the same GC strategy that the Java VM uses as well however like the Java VM the Flash VM is also slow when using Mark and sweep, which implies that code must be written better or it will hiccup when the GC kicks in. I downloaded your code and analysed it and found it to be well written, except for the gskinner code which in this case you don’t really need. Why? well because the GC just hasn’t received an opportunity to removing that object yet. This is fairly common in systems that use VM’s and GC’s. I obviously don’t work for Adobe, so I don’t have access to the C code of the Flash Player, but I’ve worked on VM’s before and a lot of them employ a memory threshold strategy, that is the GC will only start actively or aggressively working if a certain memory threshold has been met. Example, total system memory is 1024K, your running app gets an allocation of 256K but the GC doesn’t kick in until memory consumption tops 90k. Like I mentioned earlier I’m not sure if this is the case with the Flash VM but I wouldn’t be surprised if it was employed to keep things moving quickly and if this is indeed the case your code probably didn’t hit that threshold. Whatever the case be, rest assured that nothing is wrong with your code and that the behaviour you are witnessing is normal. The AS3 GC is much, MUCH better than it’s predecessors and I know for a fact that the Flash Player Engineering Team where very much aware of the shortcomings of the previous implementation. I’ll probably post a much more in-depth post on memory access in the Flash VM but for now I hope this helps. Email me if you need anything!

    Cheers mate!
    Sam

  16. 16 Samuel Agesilas said at 11:14 am on May 14th, 2008:

    Oh, I forgot to mention, that using statements like dispose() or delete does NOT work like C’s dealloc statement. Meaning that all those statements do is mark a particular object for the GC to delete them(if it can) when the GC is ready to delete them. They do not necessarily delete the object immediately.

    :)
    Cheers,
    Sam

  17. 17 Idoru said at 11:58 am on May 14th, 2008:

    Wow sam, thanks for the great ‘post’ :)
    Right you are on all the stuff you said, altough speaking with Richard Galvan and Tinic, they acknowledged that this was indeed a Leak in the VM at the time of writing the article, it has been fixed in the latest versions.
    But great post man, and thx!

  18. 18 Samuel Agesilas said at 11:13 pm on May 16th, 2008:

    Awesome!! Good thing the Flash Player team confirmed and fixed the bug. Memory Leaks are so difficult to fix but thanks to folks like yourself that bring these issue’s to light, really help the FP Team bang them out. Cheers!

  19. 19 kglad said at 4:21 pm on June 21st, 2008:

    sam, are you still checking this thread?

  20. 20 smith said at 12:07 am on July 31st, 2008:

    Regarding the code:
    var li:LoaderInfo = loader.contentLoaderInfo;
    if(li.childAllowsParent && li.content is Bitmap){
    (li.content as Bitmap).bitmapData.dispose(); // remove bitmap from memory
    }

    when running Simulate Download in Flash IDE, got eeror:
    Error: Error #2099: The loading object is not sufficiently loaded to provide this information.
    at flash.display::LoaderInfo/get childAllowsParent()
    at com.cfour.app.afl.mural::PopupWindow/disposeImage()
    at com.cfour.app.afl.mural::PopupWindow/closeHandler()

    trace(li) got null.

    Why?

  21. 21 Christopher’s Blog» Blog Archive » 两篇关于AS3内存泄漏的文章 said at 8:18 am on August 2nd, 2008:

    [...] http://www.dreaminginflash.com/2007/10/22/memory-leak-in-as3-loader-class/ [...]

  22. 22 Shadow_SB said at 7:57 am on August 14th, 2008:

    @Smith

    Did you invoke dispose after Event.COMPLETE or unload() ?

  23. 23 luisM said at 6:15 pm on February 15th, 2009:

    hi guys , very interesting , i just wanted to check this out and wrote this code :

    package {
    import flash.display.Bitmap;
    import flash.display.Loader;
    import flash.display.LoaderInfo;
    import flash.display.SimpleButton;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.net.URLRequest;

    public class memory extends Sprite
    {
    private var _loader:Array = new Array();

    private var _index:int;
    private var _total:int = 100;

    public function memory()
    {
    SimpleButton(this["btn2"]).addEventListener(MouseEvent.CLICK, loadImg);
    }

    protected function loadImg(ev:MouseEvent):void
    {
    var loader:Loader = new Loader();

    loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);

    loader.load(new URLRequest(“bg_equipo.png”));
    }

    protected function onComplete(ev:Event):void
    {
    LoaderInfo(ev.target).removeEventListener(Event.COMPLETE, onComplete);
    _loader[_index] = Loader(addChild(LoaderInfo(ev.target).loader));

    _index++;

    if ( _total == _index)
    {
    SimpleButton(this["btn1"]).addEventListener(MouseEvent.CLICK, onClick);
    trace(“FINISHED !! “);
    }
    else
    loadImg(null);
    }

    protected function onClick(ev:MouseEvent):void
    {
    SimpleButton(this["btn1"]).removeEventListener(MouseEvent.CLICK, onClick);

    var len:int = _loader.length;
    for ( var i:int ; i < len ; i++ )
    {
    var l:Loader = Loader(_loader.pop());
    if(l.contentLoaderInfo.childAllowsParent && l.contentLoaderInfo.content is Bitmap){
    (l.contentLoaderInfo.content as Bitmap).bitmapData.dispose(); // remove bitmap from memory
    }

    l.unload();
    removeChild(l);
    l = null;
    }

    _loader = null;
    _loader = new Array();
    }
    }
    }

    to compile it create a fla file with this class as documentClass and to simpleButtons called btn1 and btn2 , and change the file name.

    i load the same image 100 times and try the method i found here, i’m working on a macbook pro and in the activity monitor i checked that the memory for the firefox goes up in about 130 Mb, and when the clean method is executed the memory goes down about 50 MB, and it stays there. the whole memory is not cleaned.

    if someone finds code errors please let me know . this issue is affecting my projects.

    bye

    LM :)

  24. 24 luisM said at 6:22 pm on February 15th, 2009:

    i found some issues, please remove _index var and replace _loader[_index] for _loader.push, and , if ( _total == _index) for if ( _total == _loader.length )

    sorry

    LM :)

  25. 25 Harp said at 9:00 pm on June 2nd, 2009:

    Great thread…can anyone perhaps tell me what needs to reside in this code below that will check for and remove a loaded clip, before loading a new one? I simply need to “juggle” swf files without the memory usage just going up indefinitely but can’t seem to get it right. Thx.

    var loader:Loader = new Loader();
    addChildAt(loader,1);

    btn1.addEventListener(MouseEvent.CLICK, getFile);
    btn2.addEventListener(MouseEvent.CLICK, getFile);

    btn1.dispatchEvent(new MouseEvent(MouseEvent.CLICK)); //loads first file

    function getFile(e:MouseEvent):void {
    removeChild(loader);
    addChildAt(loader,1);
    var file = “mc” + e.target.name.substr(3) + “.swf”;
    loader.load(new URLRequest(file));

    var mem:String = Number( System.totalMemory / 1024 / 1024 ).toFixed( 2 ) + ‘Mb’;
    trace( mem );
    }

  26. 26 patCRUZ said at 4:27 pm on February 23rd, 2010:

    xpto.unloadAndStop();

    should work! ;)

  27. 27 coder said at 8:44 pm on June 6th, 2010:

    you may set a variable in the complete listener that stores the bitmap and then destroy the object later

    public function COMPLETEListener(e:Event){
    myBitmap = e.target.loader.content;
    }

    public function destroy(){
    if(myBitmap is Bitmap){
    myBitmap.bitmapData.dispose();
    }
    }

    works fine for me load some big image and see the difference in the taskmanager


Leave a Reply