Liv*_*foi 20 c# applicationdomain
当我执行AppDomain.Unload(myDomain)时,我希望它也可以执行完整的垃圾回收.
根据Jeffrey Richter在"CLR via C#"中的说法,他说在AppDomain.Unload期间:
CLR强制进行垃圾收集,回收由现在卸载的AppDomain创建的任何对象使用的内存.调用这些对象的Finalize方法,使对象有机会正确地清理自己.
根据"自定义.NET Framework公共语言运行时"中的"Steven Pratschner":
在所有终结器运行并且域中不再执行任何线程之后,CLR就可以卸载内部实现中使用的所有内存中数据结构.但是,在此之前,必须收集驻留在域中的对象.发生下一次垃圾收集后,将从进程地址空间卸载应用程序域数据结构,并将该域视为已卸载.
我误解了他们的话吗?我做了以下解决方案来重现意外行为(在.net 2.0 sp2中):
一个名为"Interfaces"的类库项目,包含此接口:
public interface IXmlClass
{
void AllocateMemory(int size);
void Collect();
}
Run Code Online (Sandbox Code Playgroud)
一个名为"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);
}
}
Run Code Online (Sandbox Code Playgroud)
一个控制台应用程序项目,它引用了"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();
}
Run Code Online (Sandbox Code Playgroud)
运行控制台应用程序时的输出是:
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
Run Code Online (Sandbox Code Playgroud)
需要注意的事项:
每个进程完成垃圾收集(只是复习)
appdomain中被卸载的对象调用了终结器,但是没有完成垃圾收集.AllocateMemory()创建的10兆字节对象只有在上面的示例中执行显式GC.Collect()之后才会被收集(或者如果垃圾收集器将在稍后的某个时间收集.
其他注意事项:XmlClass是否可以终结并不重要.在上面的示例中出现相同的行为.
问题:
为什么调用AppDomain.Unload不会导致垃圾回收?有没有办法在垃圾收集中调用该调用结果?
在AllocateMemory()内部,我计划加载将在LargeObject堆上获得的短期大型xml文档(小于或等于16 mb),并将成为第2代对象.有没有办法收集内存而不采用显式GC.Collect()或其他类型的垃圾收集器的显式编程控制?
Liv*_*foi 18
补充说明:
在与Jeffrey Richter进行一些邮件交流之后,他很友好地看了一下这个问题:
好的,我看了你的帖子.
首先,在XMLClass对象为GC并且需要两个GC来收集此对象之前,数组将不会进行GC,因为它包含Finalize方法.
其次,卸载appdomain至少执行GC的标记阶段,因为这是确定哪些对象无法访问的唯一方法,以便可以调用它们的Finalize方法.
但是,卸载GC时可能会或可能不会执行GC的紧凑部分.调用GC.CollectionCount显然不能说明整个故事.没有显示GC标记阶段确实发生过.
并且,AppDomain.Unload可能通过一些内部代码启动GC,这不会导致集合计数变量递增.我们已经知道正在执行标记阶段并且收集计数没有反映这一点.更好的测试是查看调试器中的一些对象地址,看看是否实际发生了压缩.如果确实如此(我怀疑它确实如此),那么集合计数就没有正确更新.
如果您想将此帖子作为我的回复发布到网站,您可以.
在听完他的建议并调查SOS(也删除了终结器)之后,它揭示了这个:
在AppDomain.Unload之前:
!EEHeap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0180b1f0
generation 1 starts at 0x017d100c
generation 2 starts at 0x017d1000
ephemeral segment allocation context: none
segment begin allocated size
017d0000 017d1000 01811ff4 0x00040ff4(266228)
Large object heap starts at 0x027d1000
segment begin allocated size
027d0000 027d1000 02f75470 0x007a4470(8012912)
Total Size 0x7e5464(8279140)
------------------------------
GC Heap Size 0x7e5464(8279140)
Run Code Online (Sandbox Code Playgroud)
在AppDomain.Unload之后(相同的地址,没有完成堆压缩)
!EEHeap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0180b1f0
generation 1 starts at 0x017d100c
generation 2 starts at 0x017d1000
ephemeral segment allocation context: none
segment begin allocated size
017d0000 017d1000 01811ff4 0x00040ff4(266228)
Large object heap starts at 0x027d1000
segment begin allocated size
027d0000 027d1000 02f75470 0x007a4470(8012912)
Total Size 0x7e5464(8279140)
------------------------------
GC Heap Size 0x7e5464(8279140)
Run Code Online (Sandbox Code Playgroud)
在GC.Collect()之后,地址不同表明堆压缩已完成.
!EEHeap -gc
Number of GC Heaps: 1
generation 0 starts at 0x01811234
generation 1 starts at 0x0180b1f0
generation 2 starts at 0x017d1000
ephemeral segment allocation context: none
segment begin allocated size
017d0000 017d1000 01811ff4 0x00040ff4(266228)
Large object heap starts at 0x027d1000
segment begin allocated size
027d0000 027d1000 027d3240 0x00002240(8768)
Total Size 0x43234(274996)
------------------------------
GC Heap Size 0x43234(274996)
Run Code Online (Sandbox Code Playgroud)
在得到更多结论之后,我得出的结论是它肯定是设计的,并且堆压缩不一定完成.在AppDomain卸载期间,您唯一可以确定的是,对象将被标记为无法访问,并将在下一次垃圾收集期间收集(就像我说的那样,当您卸载应用程序域时,它并没有完全完成,除非有巧合).
编辑:我也问过直接在GC团队工作的Maoni Stephens.您可以在评论的某处看过她的反应在这里.她证实这是设计上的.案例结束:)
可能是设计,但我不明白为什么你想要这种行为(显式GC.Collect).只要调用终结器,就会从终结器队列中删除对象,并且如果需要,可以进行垃圾收集(gc线程将在必要时启动).
您可以使用一些令人讨厌的非托管分配和一些繁重的互操作,或者在非托管c ++中编码,然后使用托管包装器通过C#访问它,但只要您保持在托管的.Net世界中,否则.
再看看你的架构而不是专注于尝试扮演垃圾收集器的角色更为明智.