如果垃圾收集器挂起所有托管线程,为什么这段代码会导致 System.OutOfMemoryException?

If the Garbage Collector suspends all managed threads, why does this code cause a System.OutOfMemoryException?

根据Fundamentals of garbage collection,除触发垃圾收集的线程外,所有线程在垃圾收集期间都被挂起。由于终结器在垃圾收集过程中被调用,我希望线程被挂起,直到所有终结器都被执行。因此,我希望下面的代码“需要更长的时间”才能完成。相反,它会抛出 System.OutOfMemoryException。有人可以详细说明为什么会这样吗?

class Program
{
    class Person
    {
        long[] personArray = new long[1000000];
        ~Person()
        {
            Thread.Sleep(1);
        }
    }

    static void Main(string[] args)
    {
        for (long i = 0; i < 100000000000; i++)
        {
            Person p = new Person();
        }
    }
}

在执行终结器时,在堆上创建新的 Person 对象不会也被挂起吗?

代码取自考试参考 70-483 Programming in C# (MCSD)

好的,所以我又做了一些研究,结果发现垃圾收集器并没有同步调用终结器。它执行以下操作:

  1. 冻结所有 运行 个线程。
  2. 将需要完成的项目添加到 finalizer queue
  3. 对符合条件的对象(没有终结器或已调用终结器的对象)执行垃圾回收。
  4. 解冻它冻结的线程。

之后,终结器线程与应用程序的其余部分一起在后台启动运行 ,并调用其队列中每个对象的终结器。这就是我描述的问题出现的地方。堆中的 Person 个对象将它们的引用移动到终结队列中,但它们的内存在终结实际发生之前不会被释放。最终确定在应用程序 运行 时发生(并且在堆上创建了更多对象)。

垃圾收集器在终结完成之前无法回收内存,因此在主线程(创建对象)和 GC 终结器线程(调用终结器)之间出现竞争条件。

我还通过调试 IL 代码并查看上述两个线程之间的执行切换确认了此行为。

我还在 SO 上找到了 this 描述相同行为的问题。