.NET垃圾收集器的奇怪行为

时间:2016-05-18 00:55:36

标签: c# garbage-collection

MSDN对GC.Collect()

  

所有对象,无论它们在内存中存在多长时间,都是   考虑收集; 但是,引用的对象   托管代码未收集。使用此方法强制系统   尝试回收最大可用内存量。

所以我希望在收集Parent之前不会收集仍然在Parent类中引用的Child类。

但奇怪的是,它是在收集父母之前收集的。这对我没有任何意义。

我在VS2010上编译以下代码并在框架4.0上运行它。 我得到的是:

Garbage Collector Test Code

using System;

namespace GarbageCollector
{
    class Child
    {
        public bool bInUse = true;
        public void Dispose()
        {
            Console.WriteLine("Child finished by Parent.");
            bInUse = false;
        }

        ~Child()
        {
            bInUse = false;
        }
    }

    class Parent
    {
        Child child = new Child();
        ~Parent()
        {
            if (!child.bInUse)
                Console.WriteLine("Finalizing Child that is still in use in a Parent!");

            child.Dispose();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            while (true)
            {
                for (int i=0; i<10; i++)
                {
                     Parent P = new Parent();
                }
                GC.Collect();
            }
        }
    }
}

有人可以解释一下这里发生了什么吗?

修改

我已经找到了解决问题的方法。如果你想在你的类的Finalizer中访问类成员,如果这些成员本身也有一个Finalizer,这可能是个问题。在这种情况下,在类的Finalizer可以访问它们之前,成员可能已经死了,因为GarbageCollector会以任何顺序销毁它们。 (父母或子女在父母之后的孩子)

但如果您访问没有自己的终结器的类成员,则不会出现此问题。

因此,如果您想要存储类中的句柄列表,并且想要在Finalizer中关闭这些句柄,请确保此列表类没有自己的Finalizer,否则您的句柄可能会在您之前消失可以关闭它们!

1 个答案:

答案 0 :(得分:8)

  

所以我希望在收集Parent之前不会收集仍然在Parent类中引用的Child类。

这不是一个真实的假设。 GC可以自由地收集任何对象,只要它可以证明该对象不再可以从任何将来运行的代码访问。 允许在此时收集任何对象,但可以自由地收集或保留满足该条件的任何对象。如果一个对象引用了另一个对象,但它既没有root或可从任何有根对象访问,那么GC可以按任何顺序自由删除它们,甚至可以删除子进程而不是父进程。

值得注意的是,您的代码没有显示任何内容。对象的终结器可以在其有资格收集和实际收集之间的任何时间点运行。即使运行两者的终结器,终结器运行的顺序也不是保证收集对象本身的顺序。

当然,在实践中,赔率非常高,两个对象实际上将在完全同时收集,除非其中一个对象已存在更长时间比另一个。 GC运行时将所有对象(在给定层中)视为“死”,然后将那些仍然“活着”的对象复制到新的部分中,只要有需要的内存,就会覆盖所有未复制的对象,因此,如果两个对象都在同一个GC层(很可能),那么两个位置的内存都可以在完全相同的时刻被覆盖。至于那个内存实际被覆盖的时候,甚至很难找到(如果它甚至被覆盖)。

所以最后,引用期望背后的整个概念在许多不同的层面上并不是一个明智的前提。