如果垃圾收集器挂起所有托管线程,为什么这段代码会导致 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)
好的,所以我又做了一些研究,结果发现垃圾收集器并没有同步调用终结器。它执行以下操作:
- 冻结所有 运行 个线程。
- 将需要完成的项目添加到 finalizer queue。
- 对符合条件的对象(没有终结器或已调用终结器的对象)执行垃圾回收。
- 解冻它冻结的线程。
之后,终结器线程与应用程序的其余部分一起在后台启动运行 ,并调用其队列中每个对象的终结器。这就是我描述的问题出现的地方。堆中的 Person
个对象将它们的引用移动到终结队列中,但它们的内存在终结实际发生之前不会被释放。最终确定在应用程序 运行 时发生(并且在堆上创建了更多对象)。
垃圾收集器在终结完成之前无法回收内存,因此在主线程(创建对象)和 GC 终结器线程(调用终结器)之间出现竞争条件。
我还通过调试 IL 代码并查看上述两个线程之间的执行切换确认了此行为。
我还在 SO 上找到了 this 描述相同行为的问题。
根据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)
好的,所以我又做了一些研究,结果发现垃圾收集器并没有同步调用终结器。它执行以下操作:
- 冻结所有 运行 个线程。
- 将需要完成的项目添加到 finalizer queue。
- 对符合条件的对象(没有终结器或已调用终结器的对象)执行垃圾回收。
- 解冻它冻结的线程。
之后,终结器线程与应用程序的其余部分一起在后台启动运行 ,并调用其队列中每个对象的终结器。这就是我描述的问题出现的地方。堆中的 Person
个对象将它们的引用移动到终结队列中,但它们的内存在终结实际发生之前不会被释放。最终确定在应用程序 运行 时发生(并且在堆上创建了更多对象)。
垃圾收集器在终结完成之前无法回收内存,因此在主线程(创建对象)和 GC 终结器线程(调用终结器)之间出现竞争条件。
我还通过调试 IL 代码并查看上述两个线程之间的执行切换确认了此行为。
我还在 SO 上找到了 this 描述相同行为的问题。