Main

August 7, 2009

What We Know About Unloading Modules

As of this writing, the Flex team does not know of any scenario that pins a module in memory forever assuming the developer has cleaned up all references.

If you have a module that does not unload, the steps to diagnose the problem are:

1) Make sure the module is being loaded into a child applicationDomain (use default parameters for the load() method in most cases)
2) Use the profiler to make sure there are no references to objects in the module. The most common causes of leaks are:

a. Styles. Even without any mx:Styles tag in your MXML, if a module uses a component that is not used by the main application, the module will register the default styles for that component with the StyleManager and pin the first instance of the module. You can use the -compiler.keep-generated-actionscript compiler option to see which default styles the module and main application are using. You can use the -compiler.keep-all-type-selectors compiler option on the main application to force the main application to register the default styles for every component in the defaults.css file. Prior recommendations to load style declarations via runtime CSS (StyleManager.loadStyleDeclarations) are withdrawn. Runtime CSS modules register styles in a different way so they can be unloaded and will not prevent this problem.
b. Resources. If a module uses components that use ResourceBundles that are not used by the main application, those resource bundles will get registered with the ResourceManager and pin the first instance of the module. You can use the -compiler.keep-generated-actionscript compiler option to see which resource bundles the module and main application are using. You can force the main application to have those additional resource bundles by adding the resource bundle metadata to the main application. For example, to add the "controls" resource bundle, you would add to the main application MXML file: [ResourceBundle("controls")] If you are loading your resource bundles as modules, make sure the resource modules have completed loading before loading the module that is being pinned.
c. ExternalInterface.addCallback. Modules should not use ExternalInterface.addCallback. That registers the method with the browser and there is no way to unregister it at this time. The recommended practice is to have the main application register a method that will call a function reference and put the module's method in that function reference, then set the function reference to null when the module unloads.
d. Timers and other timer mechanisms. The use of Timer, setTimeout(), and setInterval() can cause leaks. Timer's must be stopped and have their listeners removed, setTimeout and setInterval must be paired with calls to clearTimeout() and clearInterval().
e. Listeners to events from objects outside of the module. Use weak references or make sure to remove the event listeners. Remember, when you call a.addEventListener("foo", b.someMethod), it is 'a' that has a reference to 'b', not the other way. If you are listening to a parent or stage or singleton, those objects now have a reference to the object that whose method is going to get called and can cause leaks.
f. Focus. If an object in the module has focus, the FocusManager will still have a reference to the module. A good UI will move focus to some other object outside the module before removing the module.
g. RemoteObject. If a module brings in a data class that is going to be part of a server query, that class can get registered with the Player and result in a reference to the module. Link data classes into the main application when possible.
h. Loaded Images. If a module loads an image, that image must be unloaded otherwise the player will still keep some data buffers around.

3) Once there are no references to objects in the module, the Flash Player can still keep a module in memory for a while if objects in the module ever had focus. Other activity in the application, such as typing and clicking will eventually release references to the module and the module will be freed from memory. Try typing and clicking in the main application or another module and see if the pinned module gets garbage-collected.
4) Debug-versions of a module on the Debugger Player can also lead to a module being stuck in memory. Debug-versions contain debug information that can get registered with the debugger and not released. The final test is always to use release versions of the modules and application on a release version of the player.

A word (or 50) about unloadAndStop(): UnloadAndStop is a new API in player 10. It is intended to stop audio and video playback and the timeline, but does not claim to deference all references to classes in a loaded SWF and has not been proven to help modules free themselves from memory. While it may help child applications loaded via SWFLoader, it is not used by modules. Modules actually call Loader.unload() on themselves immediately upon completion of loading so that they will be available for garbage collection once all references to objects in the module have been removed. This is because a module can be used to make multiple instances of the various classes in the module and we don't want to add the overhead of tracking all of those instances or require that a developer have some other way of tracking when all references to a module have been removed so they could know when to call unload().

May 29, 2009

More on Finding Memory Leaks

It seems that the most common scenarios involving memory leaks are the ones involving loading and unloading multiple SWFs like modules and sub-applications. Every day, we learn more and more about how the player manages memory and its other idiosyncracies, so it is time for another summary.

When debugging suspected memory leaks when loading/unloading SWFs, I generally do the following:

1) Set up the app or a test harness to load and unload the SWF multiple times (at least 3) and force a garbage collection pass after each unload or unload, then use the profiler to see how many copies of the module's xxx_FlexModuleFactory or the subapplication's xxx_SystemManager are in memory. If more than 1, keep loading and unloading and see if that number continues to grow. Any module or SWF that introduces a new component with styles will register those styles with the StyleManager and stick around forever the first time it loads. You can prevent that from happening by pre-loading the styles in the main app or via a CSS module. A second copy might stay around if it was the last thing loaded because the player or FocusManager might still be hanging onto it. If you see more than 2, that's definitely a leak and you should use the profiler to find the leak.

2) After several loads and unloads, I take a memory snapshot, then do more loads and unloads and take another snapshot. I clear all filters, remove percentages, sort by class name and compare manually the number of instances of every class. They should match exactly, except maybe for a few Strings and sometimes, WeakReference. Everything else is suspect and deserves investigation.

3) Once I think I got all references to the SWF cleaned up, I next run several loads and unloads in the debugger and check the console. I am looking for lines in the debug output that start with:
[UnloadSWF]
That tells me that the player thought everything was cleaned up and unloaded the SWF. Note that it may not say that right away, even after GC requests as sometimes the player has internal references to a SWF that get cleaned up "later". If I don't see that, I go back to step 2 and compare memory snapshots looking for other things that might be leaking

4) Now that I'm convinced that even the player thinks it is ok to unload the SWF, if System.totalMemory is still increasing, the final test is to export release builds for all swfs and run them in a release player. The debugger player seems to hang onto debug information in the SWFs and can skew System.totalMemory. In recent tests, once I get past step 3, the release player's reporting of System.totalMemory is much more acceptable, capping at a much smaller and acceptable maximum memory value.

5) Once you get past that, some of you might still see memory attributed to the player still growing when using OS tools to examine the player process. That remains an open area of investigation by the player team. For Internet Explorer, one often finds that minimizing IE causes its claim on memory to shrink, implying that it is something to do with IE's memory management and not the Flash Player or your application. We don't know of any way to programatically force IE to give up that memory. We also have seen reports of other browsers reporting memory growth even though Flash thinks things should be unloaded. If you can reproduce that in a small test case, file bugs with those test cases.

October 5, 2007

Debugging Tricks

FlexBuilder 3 Beta 2 no longer makes both release and debug versions on every build, in order to speed up build times. This means that the debug version is no longer suffixed with -debug and it appears more than one of you used that to turn on debugging functionality w/o changing your code.

Also, a few folks have asked for a single line of code that could be pasted verbatim into function bodies that would report the name of the function, and a few others have wondered if there is a way to get the name of the calling function.

Assuming you are using a debugger player, the source code at this link demonstrates how to do each of these things. See setDebugFlag(), getFunctionName() and getCallingFunctionName().

Download file

private function setDebugFlag():void
{
	var e:Error = new Error();
	var s:String = e.getStackTrace();
	// trace(s);
	var i:int = s.indexOf("setDebugFlag");
	if (s.charAt(i + 14) == '[')
		debugMode = true;
}

[Bindable]
public var debugMode:Boolean = false;

private function getFunctionName(e:Error):String
{
	var s:String = e.getStackTrace();
	var i:int = s.indexOf("at ");
	var j:int = s.indexOf("()");
	return s.substring(i + 3, j);
}

private function getCallingFunctionName(e:Error):String
{
	var s:String = e.getStackTrace();
	// trace(s);
	var i:int = s.indexOf("at ");
	i = s.indexOf("at ", i + 3);
	if (i == -1)
		return "caller unknown";
	var j:int = s.indexOf("()", i + 3);
	return s.substring(i + 3, j);
}

where the latter two functions are can be used like this:

private function doSomething():void
{
	trace(getFunctionName(new Error()));
	doit();
}

private function doSomethingElse():void
{
	trace(getFunctionName(new Error()));
	doit();
}

private function doit():void
{
	trace(getFunctionName(new Error()));
	trace("   called by", getCallingFunctionName(new Error()));
}

Usual caveats apply, and the numbers and strings might have to be adjusted for different languages.

March 23, 2007

SWF is not a loadable Module

Did you get this error using modules? This error means that you tried to load the module from a different server than the one the loader is running on, and/or you don't have crossdomain.xml permission to load the module from that server.

Flash/Flex has a lot of security built in so that mean people can't use Flash/Flex to do mean things to other servers. All SWFs therefore belong to the domain of the server where that SWF lives.

For example, if we're running a SWF that lives at http://a.b.com/main.SWF, that SWF belongs to the domain "a.b.com". If you try to load a SWF from somewhere else, such as http://c.b.com/module.SWF or http://e.f.org/module.SWF, then you are loading across domains.

The Flash Player will then look in the root directory of c.b.com or e.f.org for a file called crossdomain.xml, and see if a.b.com is listed in that file (or the file lists '*" which means "everybody"). If not, you cannot load the SWF as a module because, due to other security restrictions in the Flash Player, SWF loaded from another domain without crossdomain.xml permission go in a separate SecurityDomain, which means that it will have its own ApplicationDomain which means it cannot share classes with the main SWF and thus the ModuleManager can't see the objects in the SWF as Modules.

Because this is a security-related issue, there are no real workarounds. You either have to make sure that your modules are in the same domain as the SWFs that load them, or you have to set up crossdomain.xml files so they can be used from SWFs in other domains. If you are trying to use a third-party module, you probably don't have access to the third-party's server and thus can't add yourself as an accepted domain in their crossdomain.xml file. That's annoying, but those are the rules. Server owners must either give explicit permission or decide to openly share SWFs resources on a server by listing your domain in crossdomain.xml or using '*".

ArrayCollection, Arrays and Server Data

I call this the "array of one" problem. I make a request to the server for some data, usually a list of things, and it comes back and I stuff it in a dataprovider and it displays just fine....sometimes. Sometimes I get an error about converting/coercing ObjectProxy into an Array or ArrayCollection. After further investigation it turns out that it is only if the list of things has zero or one items that I get the error.

This is an unfortunate fact-of-life when serializing objects as xml. XML is really just formatted text, so all type information is essentially lost. We don't know if the data is a String, Number, Array or Object and end up making a good guess.

For example, if I have the following data to send:

class Employee
{
var Name:String = "Alex";
var EmployeeID:Number = 123;
}

It gets serialized as a one string

"Alex123"

On the receiving side, the code sees that there isn't anything numeric about "Alex" so it leaves it as a string, and that "123" is all digits so it parses it as a Number.

Now if the data is a list of values such as

class Manager
{
var Manager:String = "Steve";
var DirectReportEmployeeIDs:Array = [123, 456, 789];
}

That might get serialized as:

Steve123456789

On the receiving side, the code sees that there are multiple so it decides to make an Array of those values.

Now if there is only one direct report, the data is going to come over as:

Steve123

and sure enough, it no longer looks like an array, does it? That's why this is the "array of one" problem.

So, how to deal with it? There is no single right answer. There are type-preserving serialization technologies but they aren't as popular. You can just check to see if the thing you think is an Array or ArrayCollection is, and if not, convert it to an array by pushing onto a new array, or you can work with the data as XML and use XMLListCollection which will see the "array of one" as an XMLList.

To check, you would add code like:

if (event.result is ObjectProxy)
list.dataProvider = [ event.result ]; // short cut for new array()
else
list.dataProvider = event.result.

To use XML and XMLListCollection, set the resultFormat="e4x".