GC.Collect()不立即回收?

5
在聊天讨论中,我写了这个控制台应用程序。

代码:

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之前,我本以为这个应用程序会崩溃。

我不太关心如何制作它。我的问题是,为什么它没有崩溃呢?


2
可能是重复的问题:https://dev59.com/N0_Ta4cB1Zd3GeqPENOB - Akhil
以下链接解释了System.GC()的行为以及可以调用它的选项:https://dev59.com/N0_Ta4cB1Zd3GeqPENOB#3499595 - Akhil
这是 GC.WaitForPendingFinalizers 的作用:http://msdn.microsoft.com/zh-cn/library/system.gc.waitforpendingfinalizers.aspx - Ry-
在测试终结器调用时间时,请加入WaitForPendingFinalizers,而不是在集合发生时进行测试。 - dkackman
4个回答

12

带有终止器的对象不能在单个垃圾回收过程中被回收。这些对象会被移动到f-reachable队列,并一直保留在那里,直到调用终止器后才能被垃圾回收。

以下代码更好,但无论如何也不应依赖它:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

对我来说,即使是为了测试目的,在终结器中抛出异常似乎也太过残酷。

此外,终结器的一个有趣副作用是:具有终结器的对象仍然可以“复活”(有效地防止其被垃圾回收),如果在终结器中存储了this引用(将其分配给某个静态变量)。


我发现如果我睡眠大约100毫秒,那么在我到达WriteLine之前,终结器就会运行。终结器被调用得相当快(但不够快)。您对这种行为有什么评论吗? - Kendall Frey
是的,这是因为 finalizer 是在单独的线程上调用的。 - max

6

你读过文档吗?

使用此方法尝试回收所有无法访问的内存。

这不是命令,而是请求,可能会按照您的意愿生效也可能不会。通常这不是一个好主意(有时候某些过程创建了许多小的、短寿命的对象,这种情况下调用GC.Collect或许会有利,但这种情况很少见)。

既然您似乎并未尝试解决实际问题,而是在玩弄GC,那么这就是我能提供的最好建议。


3
在大多数垃圾收集器的实现中,在垃圾回收周期期间,没有托管用户代码可以运行。Finalize 方法被视为用户代码。虽然系统理论上可以在Finalize 方法执行时冻结所有其他用户代码,但这种行为会增加多核系统上的垃圾回收成本,并增加死锁的可能性。为避免这些问题,系统不会将Finalize 方法作为垃圾回收的一部分运行,而是构建一个需要运行其Finalize 方法的对象列表(该列表称为“freachable队列”)。列表本身被认为是根引用,因此由freachable队列中的对象引用的任何对象都将被视为强根引用,至少直到系统从队列中检索freachable对象、运行其Finalize 方法并丢弃引用为止。
微软早期关于终结的文档非常令人困惑,因为它暗示当那些方法运行时,finalizable对象所持有引用的对象可能不存在。事实上,所有这样的对象都保证存在;不确定的是它们是否已经运行了其Finalize方法。

0
  1. GC.Collect()会将那些没有被引用且具有终结器方法的对象放入终结器队列中。
  2. 它将通过调用finalize方法来清除终结器队列。

如果以上两点清楚,那么你的代码在静态方法中持有SomeClass的引用。这意味着它仍然存活,直到程序的主方法执行。

如果你想在打印“done”之前使你的应用程序崩溃,那么首先将你的SomeClass对象置为null,然后调用GC.Collect。它会将你的对象放入终结器队列中,但是再次取决于GC何时清除该队列。如果你希望GC清除该队列并调用finalizer,则调用GC.WaitForPendingFinalizers()。你的线程将等待直到finalizer被调用,然后才会继续执行。我修改了你的代码以获得所需的输出。我没有抛出异常,而是在finalizer中打印了一条语句。

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...");
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接