Friday, June 12, 2009

Flex Memory Issue #4: Modules and Application Domains

“ApplicationDomains are pretty much a neverending source of confusion and suffering…”

- Roger Gonzales, helped in the design of Application Domains in Flex

If you are using modules in Flex whether you are aware of it or not, you are using Application Domains. In particular I have had several issues with them and their effects on memory. An Application Domain is a container for discrete groups of classes, which are used to partition classes that are in the same security domain.

What? Yes, I know. That definition is from Adobe’s asdoc for the ApplicationDomain class, and it really doesn't tell you what it has to do with modules. For a start Roger Gonzales did a great job of explaining the history and purpose of the Application Domain on his blog, located at http://blogs.adobe.com/rgonzalez/2006/06/applicationdomain.html.

To attempt to summarize the Application Domain it is essentially a way to contain AS3 definitions, which effects how modules load and unload. With modules Application Domains let you do one of two things, which I refer to as the “Current Domain Method” and the “Copied Domain Method”

Current Domain Method

Example Usages:

var info:IModuleInfo = ModuleManager.getModule("com/foo/FooModule.swf");
info.load(ApplicationDomain.currentDomain);

This loads a module into the current Application Domain, so that when it unloads the class definition stays in memory but the rest of the module related instances are supposed to go. The next time the module is loaded the class definitions are already there so it loads faster.

For example if you have an Application that has 3 modules and you load one module at a time using the Current Domain Method, unload them, and load the next module in the sequence you get the following memory profile assuming you force garbage collection (http://jvalentino.blogspot.com/2009/05/flex-memory-issue-3-garbage-collection.html) after each unload:

 image

* Assumes modules are all the same size, garbage collection is forced after unload, memory snapshot is taken using System.totalMemory, and memory snapshot is taken between unload of previous and load of new

Notice that the memory peaks after all of the modules are loaded, levels off when those three modules are again loaded, and then only drops down slightly after all the modules are unloaded. The remaining memory isn’t just Flash Player stuff, it is also the definitions for the modules classes. This means that when using the Current Domain Method, modules will never truly unload.

For another example if you look at the time it takes those module to load for the 3 module scenario using the Current Domain Method, you get the following:

image

The initial load times for each module are much greater than the amount of time for next time the same module is loaded. This is because the definitions for those modules are stored in the current Application Domain for the application when using the Current Domain Method.

Theoretically, when would one use the Current Domain Method?

You would want to use the current domain method if you were not worried about the user loading enough modules to cause the application to go out of memory. When you are using this method you are essentially caching the module definitions when you load them, allowing for the module to be loaded much faster the next time it is needed. To summarize it requires more memory in order to achieve faster load times.

Copied Domain Method

This is what happens when you use the load function of the ModuleManager and specify the parameter as a new Application Domain using the current Application Domain. This is also the default behavior when not using a parameter at all.

Example Usage:

var info:IModuleInfo = ModuleManager.getModule("com/foo/FooModule.swf");
info.load();

or…

var info:IModuleInfo = ModuleManager.getModule("com/foo/FooModule.swf");
info.load(new ApplicationDomain(ApplicationDomain.currentDomain));

This loads the module into a new Application Domain so that they are no references to keep module definitions from unloading. The result is that when you unload a module all of the related classes are supposed to entirely unload. The amount of time required to load a module never changes, because it has to be completely loaded each time.

For example if you have an Application that has 3 modules and you load one module at a time using the Copied Domain Method, unload them, and load the next module in the sequence you get the following memory profile assuming you force garbage collection (http://jvalentino.blogspot.com/2009/05/flex-memory-issue-3-garbage-collection.html) after each unload:

image 

* Assumes modules are all the same size, garbage collection is forced after unload, memory snapshot is taken using System.totalMemory, and memory snapshot is taken between unload of previous and load of new

Notice that when the all modules are unloaded the memory is supposed to go back down to where it was before the first module was loaded.

For another example if you look at the time it takes those module to load for the 3 module scenario using the Copied Domain Method, you get the following:

 image

The amount of time that it takes to load each module never changes, because each time the module has to be completely loaded. It is my understanding that the reason for this is because when using the Copied Domain Method you are not adding AS3 definitions to the current domain, so those definitions can fully unload when the module is unloaded.

Theoretically, when would one use the Copied Domain Method?

You want want to use this method if your application was under some kind of memory constraint, or if there were so many modules that it could cause the Flash VM to run out of memory. The down side to this is that you have to fully load each module every time you need it, whether it has already been loaded or not.

Current Domain Method versus Copied Domain Method in the real world

In large and stylistically complex applications in the real world, the copied domain method has provided me with a world of trouble when it comes to memory. The memory behavior is erratic to say the least in the following ways:

  • Even when forcing garbage collection the memory jumps around erratically like nothing is being done at all.
  • If you run the same exact scenario over and over again you get different results. One time memory averages 30 MB, another 50 MB, and another time it just runs out of memory

I know what you are thinking, “there are other memory leaks in the application!” That would be incorrect, as the profiler shows no leaking and if you change to the Current Domain Method the problem is gone in the particular scenario in which this happens. It is that point which I became completely confused and I was even able to talk to Adobe about it through a third party, but I was never able to get an answer.

The scenario in particular was the loading and unloading of two different modules over and over again about 100 times. Load Module 1, unload Module 1 and load Module 2, unload Module 2 and load Module 1, etc.

When using the Copied Domain Method each time I ran this scenario I got different results:

image

For example the first attempt at this scenario was fine, the second attempt ran out of memory, and the third attempt went up in memory and never came back down.

If you take that same exact scenario and make it use the Current Domain Method you get the following:

image

Using the Current Domain Method seemed to have fixed the memory issue in this scenario, but by using this method I was stuck with modules with definitions that never get unloaded.

6 comments:

Tom said...

Hi John,

Just wondering which versions of Flash Player and which browsers you ran your experiments on? I have observed quite different behavior between Flash Player and Flash Debug Player, with the former releasing memory back to the OS more efficiently.

Best,
Tom

Tom said...

Just noticed part 1 of your series, "The debug player always leaks"! ;-)

John Valentino said...

Flash Player versions 9.124and 10.0.12.36 non-debug with Internet Explorer 6 and 7.

The debug player always leaks with modules and the memory behavior in general is quite different. I even wrote something about it earlier: http://jvalentino.blogspot.com/2009/05/flex-memory-tip-1-debug-player-always.html

John Valentino said...

There are so many of them it is hard to keep track. There are a total of 9 in which I intended to write about, but with every post people keep sending me more.

jhalm said...

I think there is an error in your examples. The default behaviour if you omit the ApplicationDomain parameter in IModuleInfo.load is to load the Module into a child domain.
So the example without param should be in the "Copied Domain Method" section.

Copied from the implementation in the Flex SDK:

var c:LoaderContext = new LoaderContext();
c.applicationDomain =
applicationDomain ?
applicationDomain :
new ApplicationDomain(ApplicationDomain.currentDomain);

John Valentino said...

Thanks for catching that with the default usage. I changed the posting to reflect it.

I looked it up at one time in the code and just must have switched it in my head.