当GC调用Object.Finalize时,子对象是否仍然存活?

时间:2014-08-25 07:01:57

标签: c# .net garbage-collection finalizer

说,我有这个班:

class Test
{
    readonly object _child = new Object();

    // ...

    ~Test()
    {
        // access _child here
        // ...
    }
}

当垃圾收集器调用_child时,~Test对象保证仍然存活吗?或者我应该首先在构造函数中“固定”_childGCHandle.Alloc吗?

3 个答案:

答案 0 :(得分:2)

作为readonly字段_child,您无法丢失其引用(除非您通过反射将其设置为null)。这意味着直到Test被垃圾收集_child才能保留在内存中。

此外,您使用的是在垃圾回收之前调用的Finalizer,只有在下一次传递时,对象的内存才会被回收,此时_child将会被回收活。换句话说,当调用Finalize方法时,_child将处于活动状态并且可以安全地访问它。

终结者被称为并不意味着将回收内存,如果您执行类似以下操作的Finalize将被调用,但内存将无法回收

class Test
{
    readonly object _child = new Object();

    private static Test evilInstance;

    ~Test()
    {
        evilInstance = this;//Do something crazy
        //This resurrects this instance, so memory will not be reclaimed.
    }
}

当您处理托管代码时,几乎不需要终结器,它会为垃圾收集器添加额外的工作,并且如上所述也会发生奇怪的事情。

更新:如果仅对_child使用lock,则可以安全使用,因为_child实例不会为空,这意味着它指向有效的参考。 Monitor.EnterMonitor.Exit只关心使用它绝对安全的引用(仅用于锁定)。

  

如果您需要在之后调用孩子的终结者,该怎么办?   调用Test's终结器?

有一种解决方法:您可以从Child继承SafeHandle类,这样就可以了。它会确保TestChild同时超出范围,它将首先调用Test's终结器,因为Child继承自SafeHandle这延迟了它的最后定稿。但是,IMO并不依赖于此。因为其他程序员与你合作可能不会知道这会导致误解。

  

这个关键终结器也有一个弱排序保证,声明如果一个普通的可终结对象和一个关键的可终结对象同时无法访问,那么普通对象的终结器首先运行

来自SafeHandle: A Reliability Case Study

答案 1 :(得分:1)

到目前为止,最充分的解释可以找到恕我直言here(参见卡尔森的回答),以便总结OP的答案:

在对象完成期间,任何可到达的(子对象或外部对象)仍将在内存中可用,但此对象的状态将取决于此对象是否已自行完成 - 因为没有特定的终止顺序在终结队列中的对象之间。

简而言之,引用帖子:

  

您无法访问对象引用的任何对象,它们具有终结器,因为您无法保证在终结器运行时这些对象将处于可用状态。对象仍将存在于内存中,而不会被收集,但它们可能已经关闭,终止,最终确定等。

然而,您可以在对象的终结阶段安全地使用任何其他可到达的对象(没有最终确定),例如一个字符串。实际上,这意味着使用任何不可实现IDisposable接口的可到达对象是安全的。

答案 2 :(得分:0)

我创建了一个简单的测试:

using System;

namespace ConsoleApplication1
{
    class Program

    {
        class Child
        {
            public override string ToString()
            {
                return "I am a child!";
            }

            ~Child()
            {
                Console.WriteLine("Child finalized");
            }
        }

        class Test
        {
            readonly object _child = new Child();
            readonly WeakReference _ref;

            public Test()
            {
                _ref = new WeakReference(_child);
            }

            // ...

            ~Test()
            {
                Console.WriteLine("Test finalized, child: " 
                    + _child.ToString() + ", is alive: " 
                    + _ref.IsAlive);
            }
        }

        static void Main(string[] args)
        {
            var test = new Test();
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            Console.ReadLine();
        }
    }
}

输出(发布版本):

Child finalized
Test finalized, child: I am a child!, is alive: False

因此,_child首先完成。所以,我需要在构造函数中执行GCHandle.Alloc(_child),并在终结器中执行GCHandle.Free之后执行_child

要回答评论,为什么我需要所有这些:_child在从终结器调用以访问非托管资源的方法中引用。该方法执行lock (_child) { ... },也可以从终结器外部调用它。