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
Did you ever find a fix for this?
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
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.
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
}
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.
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.
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.
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?
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.
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.
[...] version only). Previous to this method the only known way to force the garbage collection was the localConnection hack so this is good [...]
[...] 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 [...]
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.
0e28503c2640
0e28503c26406d129e13
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
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
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!
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!
sam, are you still checking this thread?
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?
[...] http://www.dreaminginflash.com/2007/10/22/memory-leak-in-as3-loader-class/ [...]
@Smith
Did you invoke dispose after Event.COMPLETE or unload() ?
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
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
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 );
}
xpto.unloadAndStop();
should work!
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