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?

4 Comments

  • I think the remark only applies to when you don't unload the AppDomain. So if you load an assembly into an AppDomain and instantiate a bunch of the types contained it in and eventually let them all go out of scope - all those instances will be cleaned by the GC, but the assembly itself will remain loaded. This is not the case when you kill off an entire AppDomain however, since that more or less signals you won't be needing any of it, assemblies, types, whatever, so it'll clear everything up.

  • There is a setting (application configuration I think) that specifies how the assemblies installed in the GAC are shared accross app doamins. In this case the assembly will only be unloaded when the last app domain that uses it is disposed of. This is only a problem if you want to reload sharred assemblies. Say multiple ASP.NET applications are using a GAC deployed assembly.



    IIRC there are 3 settings for how GAC deployed assemblies should be shared accross app domains...



    1) Share all assemblies accross app domains. 2) Share only system assemblies.

    3) Don't share any assemblies.



    Hopefully someone will fill in the blanks and say what/where the setting is! ;)

  • Jeroen is right - it applies to when the appdomain wasn't unloaded. I'll change my blog to make that more clear - thanks for pointing it out.



    There are several ways of setting with the CLR which assemblies are domain-neutral. I believe that ASP.NET uses mscoree.dll's API for binding to the runtime, which allow it to be set.

  • Hi Suzanne - now that re-read your original entry, it all makes sense to me!



    Thanks for taking the time out to look into this.



    Matthew

Comments have been disabled for this content.