在聊天讨论过程中,我编写了这个控制台应用程序。
using System;
class Program
{
static void Main(string[] args)
{
CreateClass();
Console.Write("Collecting... ");
GC.Collect();
Console.WriteLine("Done");
}
static void CreateClass()
{
SomeClass c = new SomeClass();
}
}
class SomeClass
{
~SomeClass()
{
throw new Exception();
}
}
Collecting... Done
Unhandled Exception: System.Exception: Exception of type 'System.Exception' was
thrown.
at SomeClass.Finalize()
我希望应用程序在 Done
打印之前崩溃。
我不太关心如何制作它。我的问题是,为什么不呢?
答案 0 :(得分:12)
无法在单个垃圾收集过程中收集具有终结器的对象。这些对象被移动到f-reachable
队列,并保持在那里直到调用终结器。只有在那之后它们才能被垃圾收集。
以下代码更好,但你不应该依赖它:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
此外,即使是出于测试目的,在终结器中抛出异常对我来说似乎太残酷了。
此外,终结器的有趣副作用:如果在终结器中存储this
引用(将其分配给某个静态变量),则具有终结器的对象仍然可以“自我复活”(有效地防止自身的垃圾收集)。
答案 1 :(得分:6)
您是否阅读过the documentation?
使用此方法尝试回收所有无法访问的内存。
这不是一个命令,它是一个请求,可能会或可能不会按您的意愿运行。无论如何,这通常不是一个好主意(有时很多很小的短期对象是由某些过程创建的,在这种情况下,可能对调用GC.Collect
有益,但这很少见。)
由于您似乎没有尝试解决实际问题,而是使用GC来解决这个问题,因此这是我提供的最佳建议。
答案 2 :(得分:3)
在最常见的垃圾收集器实现中,在垃圾收集周期中不能运行托管用户代码。 Finalize
方法计为用户代码。虽然理论上系统可能在执行Finalize
方法时冻结所有其他用户代码,但这种行为会增加多核系统上垃圾收集的表观成本,并且还会增加死锁的可能性。为了避免这些问题,系统不会将Finalize
方法作为垃圾收集的一部分运行,而是构建一个需要运行Finalize
方法的对象列表(该列表称为“可释放队列” “)。列表本身被认为是一个带根引用,因此,至少在系统从队列中检索可释放对象之前,任何由可释放队列中的对象引用的对象都将被视为强根,运行其{{ 1}}方法,并丢弃引用。
Microsoft关于最终化的早期文档非常混乱,因为它表明当这些方法运行时,可终结对象持有引用的对象可能不存在。实际上,所有这些对象都保证存在;不确定的是他们是否已经运行了Finalize
方法。
答案 3 :(得分:0)
如果以上两点清楚,那么您的代码在静态方法中保存对SomeClass的引用。这意味着它在程序的主要方法执行之前仍然存在。
如果您希望应用程序在打印“完成”之前崩溃,那么首先使SomeClass对象无效,然后调用GC.Collect。它会将你的对象放在终结器队列中,但GC也希望何时清除该队列。如果希望GC清除该队列并调用终结器,则调用GC.WaitForPendingFinalizers()。你的线程将一直等到你的终结器被调用,然后它将继续。我修改了你想要的输出代码。我没有抛出异常而是在终结器中打印了一个语句。
class Program
{
static void Main(string[] args)
{
CreateClass();
Console.Write("Collecting... ");
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Done");
Console.ReadLine();
}
static void CreateClass()
{
SomeClass c = new SomeClass();
c = null;
}
}
class SomeClass
{
~SomeClass()
{
Console.WriteLine("Finalized...");
}
}