首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >为什么调用AppDomain.Unload不会导致垃圾回收?

为什么调用AppDomain.Unload不会导致垃圾回收?
EN

Stack Overflow用户
提问于 2010-04-28 01:43:34
回答 2查看 6.8K关注 0票数 21

当我执行AppDomain.Unload(myDomain)时,我希望它也会执行完整的垃圾收集。

根据杰弗里·里希特在"CLR via C#“中的说法,他在AppDomain.Unload中说:

CLR强制执行垃圾回收,回收由现在已卸载的AppDomain创建的任何对象所使用的内存。这些对象的Finalize方法将被调用,从而使这些对象有机会正确地自我清理。

根据“定制.NET框架公共语言运行时”中的"Steven Pratschner“:

在所有终结器都已运行并且域中不再有线程在执行之后,CLR准备卸载内部实现中使用的所有内存中的数据结构。但是,在此之前,必须收集驻留在域中的对象。在下一次垃圾收集发生后,应用程序域数据结构将从进程地址空间中卸载,并且该域被视为已卸载。

我是不是误解了他们的话?我执行了以下解决方案来重现意外行为(在.net 2.0 sp2中):

一个名为"Interfaces“的类库项目,其中包含以下接口:

代码语言:javascript
复制
   public interface IXmlClass
    {
        void AllocateMemory(int size);

        void Collect();
    }

一个名为"ClassLibrary1“的类库项目,它引用了”接口“,并包含这样的类:

代码语言:javascript
复制
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“项目并执行以下逻辑的控制台应用程序项目:

代码语言:javascript
复制
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();
}

运行控制台应用程序时的输出为:

代码语言:javascript
复制
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

注意事项:

  1. 垃圾收集是按进程进行的(只是卸载了appdomain中的一个refresher)
  2. Objects,调用了终结器,但不进行垃圾收集。只有在上面的示例中执行了显式GC.Collect()之后(或者如果垃圾收集器稍后会这样做),才会收集由AllocateMemory()创建的10MB对象。

其他注意事项: XmlClass是否可终结化并不重要。在上面的示例中也出现了相同的行为。

问题:

  1. 为什么调用AppDomain.Unload不会导致垃圾回收?有没有办法让这个调用导致垃圾collection?
  2. Inside AllocateMemory()我计划加载生命短暂的大型xml文档(小于或等于16mb),这些文档将出现在LargeObject堆中,并且将是第二代对象。有没有办法在不使用显式GC.Collect()或垃圾收集器的其他类型的显式编程控制的情况下收集内存?
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2010-04-29 15:19:58

其他注意事项:

在与Jeffrey Richter进行了一些邮件交流后,他很友好地看了一下这个问题:

好的,我读了你的帖子。

首先,在对XMLClass对象进行GC之前,不会对该数组进行GC,并且需要两个GC来收集该对象,因为它包含一个Finalize方法。

其次,卸载appdomain至少会执行GC的标记阶段,因为这是确定哪些对象不可访问的唯一方法,以便可以调用它们的Finalize方法。

但是,GC的紧凑部分可能会在卸载GC时完成,也可能不会完成。调用GC.CollectionCount obvious并不能说明全部情况。这并不表明GC标记阶段确实发生了。

而且,AppDomain.Unload可能会通过一些内部代码启动GC,这不会导致集合计数变量递增。我们已经知道了一个事实,即正在执行标记阶段,并且集合计数没有反映这一点。更好的测试方法是查看调试器中的一些对象地址,看看是否确实发生了压缩。如果是这样的话(我怀疑是这样),那么集合计数就是没有被正确更新。如果你想把这个作为我的回复发布到网站上,你可以这样做。

在接受了他的建议并研究了SOS (也删除了finalizer)之后,它揭示了这一点:

在AppDomain.Unload之前:

代码语言:javascript
复制
!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)

在AppDomain.Unload之后(相同的地址,没有进行堆压缩)

代码语言:javascript
复制
!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)

在GC.Collect()之后,不同的地址表示堆压缩已经完成。

代码语言:javascript
复制
!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)

在更多的sos之后,我得出的结论是,它肯定是设计出来的,堆压缩不是必须完成的。在AppDomain卸载期间,您唯一可以真正确定的是对象将被标记为不可访问,并将在下一次垃圾收集期间被收集(就像我说过的,除非巧合,否则在卸载应用程序域时不会准确地进行垃圾回收)。

编辑:我也问过毛尼·斯蒂芬斯,他直接在GC团队工作。你可以在评论here的某个地方读到她的回复。她证实这是设计出来的。案例结案:)

票数 20
EN

Stack Overflow用户

发布于 2010-04-28 02:05:08

  1. 可能是故意的,但我不明白为什么你想要这种行为(显式GC.Collect)。只要终结器被调用,这些对象就会从终结器队列中移除,并准备好在需要时进行垃圾回收(GC线程将在必要时启动)。

  1. 您可以使用一些讨厌的非托管分配和一些繁重的互操作,或者在非托管c++中编写代码,然后使用托管包装器通过C#访问它,但只要您停留在托管.Net世界中,就不会。

更明智的做法是重新审视您的体系结构,而不是专注于扮演垃圾收集器的角色。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/2723579

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档