当我执行 AppDomain.Unload(myDomain) 时,我期望它也会进行完整的垃圾回收。
根据 Jeffrey Richter 在《CLR via C#》一书中的说法,在 AppDomain.Unload期间:
CLR 强制要求进行垃圾回收,以回收由已卸载的 AppDomain 创建的任何对象使用的内存。对于这些对象,将调用 Finalize 方法,使得这些对象有机会适当地清理自身。
根据 "Steven Pratschner" 在 "Customizing .NET Framework Common Language Runtime" 中的说法:
在所有终结器运行并且域中不再有更多线程正在执行之后,CLR 准备卸载用于内部实现的所有内存数据结构。但在此发生之前,必须收集驻留在该域中的对象。在下一次垃圾回收发生之后,应用程序域数据结构将从进程地址空间卸载,并且该域被视为已卸载。
我是否误解了他们的话?我用以下解决方案重现了意外行为(在 .net 2.0 sp2 中):
一个名为“Interfaces”的类库项目,其中包含此接口:
public interface IXmlClass
{
void AllocateMemory(int size);
void Collect();
}
一个名为“ClassLibrary1”的类库项目,引用了“Interfaces”,并包含这个类: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);
}
}
一个控制台应用程序项目,它引用“Interfaces”项目并执行以下逻辑:
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();
}
在控制台应用程序运行时的输出为: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
需要注意的事项:
垃圾回收是按进程执行的(进行一次提醒)
在卸载应用程序域中的对象时,会调用终结器但不会进行垃圾回收。上述示例中通过AllocateMemory()创建的10兆字节的对象只有在执行显式的GC.Collect()(或者垃圾回收器在以后某个时间内执行)后才能被回收。
其他注释:XmlClass是否可终结化并不重要。在上面的示例中,发生相同的行为。
问题:
为什么调用AppDomain.Unload不会导致垃圾收集?有没有办法使该调用导致垃圾收集?
在AllocateMemory()内部,我计划加载短暂的大型XML文档(小于等于16 MB),这些文档将放在LargeObject堆上,并且将成为第2代对象。有没有办法在不使用显式的GC.Collect()或其他形式的垃圾收集器控制的情况下对其进行内存回收?