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

Cha*_*sis 5 .net c# 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Person当终结器执行时,堆上新对象的创建是否也会被暂停?

代码取自考试参考 70-483 C# 编程 (MCSD)

Cha*_*sis 3

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

  1. 冻结所有正在运行的线程。
  2. 将需要终结的项目添加到终结器队列中。
  3. 对符合条件的对象(没有终结器或已调用终结器的对象)执行垃圾收集。
  4. 解冻冻结的线程。

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

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

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

主线程和 GC Finalizer 线程并行运行

我还在 SO 上发现了这个问题,它描述了相同的行为。