Unloading assemblies
One of my favourite bloggers, Suzanne Cook, ran an article back in July called Unloading an Assembly. For some of you, it may be a surprise to know that you can only unload an assembly if you unload the entire appdomain that it is loaded into.
This wasn't a surprise to me, but this statement was:
“There's no way to unload an individual assembly without unloading all of the appdomains containing it. This can by done by calling AppDomain.Unload() (or UnloadDomain() on the unmanaged hosting API) for each AppDomain that has it loaded.
“I should point out that this means that, even if your managed Assembly/Module/etc. objects go out of scope, GC may clean the objects up, but the actual file will remain loaded. (That's better, in general, anyway. Loading a file, especially by http, is too expensive to be done over and over, when it can just be cached.)”
Now, call me stupid, but I read this as even if I unload an appdomain, the memory allocated to the file as it's read into the process will not be freed up. This was a particular problem for me yesterday, as we had a situation where we had a server process that over time would generate code for, compile, load and use dynamic assemblies. We didn't want this process eventually collapsing due to a memory leak. I read Suzanne's comment here as that even if I create a new appdomain for the generated assembly, load it and then unload the appdomain, the process would never free up the memory used to store the image of the assembly file, hence the fact that our server process would eventually eat all the memory on the box and die.
We did some tests to see if this was true. Namely we wrote a little app that spat 250 largish assemblies (between 1.5 and 2.5MB) containing random Console.WriteLine statements. We then wrote an application that loaded each one in turn into its own appdomain, created an instance of a type in the assembly and called a known method. If we unloaded the appdomain, memory use (as reported by TaskManager) remained constant. (Around 40MB, I believe). If we did not unload the appdomain, memory crept up towards the 500MB mark, and the whole process slowed down terribly. In my opinion, this shows that the memory allocated to a loaded assembly file is recycled as the process demands more memory to load new assemblies into.
I guess I must have misunderstood what Suzanne was saying... Anyone know what subtlety I've missed in her comments?