When I perform a AppDomain.Unload(myDomain) I expect it to also do a full garbage collection.
According to Jeffrey Richter in "CLR via C#" he says that during an AppDomain.Unload:
The CLR forces a garbage collection to occur, reclaiming the memory used by any objects that were created by the now unloaded AppDomain. The Finalize methods for these objects are called, giving the objects a chance to clean themselves up properly.
According to "Steven Pratschner" in "Customizing .NET Framework Common Language Runtime":
After all finalizers have run and no more threads are executing in the domain, the CLR is ready to unload all the in-memory data structures used in the internal implementation. Before this happens, however, the objects that resided in the domain must be collected. After the next garbage collection occurs, the application domain data structures are unloaded from the process address space and the domain is considered unloaded.
Am I misinterpreting their words? I did the following solution to reproduce the unexpected behavior (in .net 2.0 sp2):
An class library project called "Interfaces" containing this interface:
public interface IXmlClass
{
void AllocateMemory(int size);
void Collect();
}
A class library project called "ClassLibrary1" which references "Interfaces" and contains this class:
public class XmlClass : MarshalByRefObject, IXmlClass
{
private byte[] b;
public void AllocateMemory(int size)
{
this.b = new byte[size];
}
public void Collect()
{
Console.WriteLine("Call explicit GC.Collect() in " + AppDomain.CurrentDomain.FriendlyName + " Collect() method");
GC.Collect();
Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
}
~XmlClass()
{
Console.WriteLine("Finalizing in AppDomain {0}", AppDomain.CurrentDomain.FriendlyName);
}
}
A console application project which references "Interfaces" project and does the following logic:
static void Main(string[] args)
{
AssemblyName an = AssemblyName.GetAssemblyName("ClassLibrary1.dll");
AppDomain appDomain2 = AppDomain.CreateDomain("MyDomain", null, AppDomain.CurrentDomain.SetupInformation);
IXmlClass c1 = (IXmlClass)appDomain2.CreateInstanceAndUnwrap(an.FullName, "ClassLibrary1.XmlClass");
Console.WriteLine("Loaded Domain {0}", appDomain2.FriendlyName);
int tenmb = 1024 * 10000;
c1.AllocateMemory(tenmb);
Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
c1.Collect();
Console.WriteLine("Unloaded Domain{0}", appDomain2.FriendlyName);
AppDomain.Unload(appDomain2);
Console.WriteLine("Number of collections after unloading appdomain: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
Console.WriteLine("Perform explicit GC.Collect() in Default Domain");
GC.Collect();
Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
Console.ReadKey();
}
The output when running the console application is:
Loaded Domain MyDomain
Number of collections: Gen0:0 Gen1:0 Gen2:0
Call explicit GC.Collect() in MyDomain Collect() method
Number of collections: Gen0:1 Gen1:1 Gen2:1
Unloaded Domain MyDomain
Finalizing in AppDomain MyDomain
Number of collections after unloading appdomain: Gen0:1 Gen1:1 Gen2:1
Perform explicit GC.Collect() in Default Domain
Number of collections: Gen0:2 Gen1:2 Gen2:2
Things to notice:
Garbage collection is done per process (just a refresher)
Objects in the appdomain that gets unloaded have the finalizer called but garbage collection is not done. The 10 megabyte object created by AllocateMemory() will only be collected after performing an explicit GC.Collect() in the above example (or if the garbage collector will at some time later.
Other notes: it doesn't really matter if XmlClass is finalizable or not. The same behavior occurs in the above example.
Questions:
Why does calling AppDomain.Unload doesn't result in a garbage collection? Is there any way to make that call result in a garbage collection?
Inside AllocateMemory() I plan to load short lived large xml documents (less or equal to 16 mb) that will get on LargeObject heap and will be generation 2 objects. Is there any way to have the memory collected without resorting to explicit GC.Collect() or other kind of explicit programmatic control of garbage collector?
Probably by design, but I don't understand why you want this behaviour (explicit GC.Collect). As long as the finalizers are called, the objects are removed from the finalizer queue and are ready to be garbage collected if required (the gc thread will kick in when necessary).
You can probably use some nasty unmanaged allocation and some heavy interop, or code it in unmanaged c++ and then use a managed wrapper to access it through C#, but as long as you stay within the managed .Net world, no.
It is more wise to take a second look at your architecture instead of focusing on trying to play the role of the garbage collector.
Additional Notes:
After some mail exchange with Jeffrey Richter who was kind enough to have a look at the question:
After taking his advice and looking into SOS (also removed the finalizer) it revealed this:
Before AppDomain.Unload:
After AppDomain.Unload (same addresses, no heap compaction was done)
After GC.Collect(), addresses differ indicating heap compaction was done.
After more sos the conclusion I've reached is that it is surely by design, and that heap compaction is not necessarily done. The only thing you can really be sure during an AppDomain unload is that objects will get to be marked as unreachable and will be collected during the next garbage collection (which like I said, it's not done exactly when you unload your application domain, unless there's a coincidence).
EDIT: I've also asked Maoni Stephens, who works directly in the GC team. You can read her response somewhere in the comments here. She confirms that it is by design. Case closed :)