需要解释当对象执行方法时对象如何被垃圾收集

时间:2011-03-08 10:16:16

标签: .net garbage-collection

在他的博客When does an object become available for garbage collection?中,Reymond Chen写道

  

对象可以符合条件   执行期间的集合   关于那个对象的方法。

此外,Curt Nichols通过this example

展示了同样的观点
public class Program
    {
        static void Main(string[] args)
        {
            new TestClass().InstanceMethod();

            Console.WriteLine("End program.");
            Console.ReadLine();
        }
    }

    public sealed class TestClass
    {
        private FileStream stream;

        public TestClass()
        {
            Console.WriteLine("Ctor");

            stream = new FileStream(Path.GetTempFileName(), FileMode.Open);
        }

        ~TestClass()
        {
            Console.WriteLine("Finializer");

            stream.Dispose();
        }

        public void InstanceMethod()
        {
            Console.WriteLine("InstanceMethod");

            StaticMethod(stream);
        }

        private static void StaticMethod(FileStream fs)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine("StaticMethod"); 
            var len = fs.Length;
        }
    }

输出符合预期 -

Ctor
InstanceMethod
Finalizer
StaticMethod

ObjectDisposedException is thrown

在此示例中,我无法理解GC如何收集临时TestClass对象,因为其成员streamStaticMethod引用。

是的,雷蒙德说明了

  

GC并不是追踪根源,而是   关于删除没有的对象   更多使用

但是,在此示例中仍然使用TestClass对象,不是吗?

请解释一下GC在这种情况下收集TestClass对象的权利如何?此外,更重要的是,开发人员应如何防范这些情况?

4 个答案:

答案 0 :(得分:6)

  

我无法理解GC如何   可以收集临时的TestClass   对象,因为它的成员流是   被StaticMethod引用。

StaticMethod实际上持有TestClass实例的stream成员的引用 - 它持有对引用的引用(就GC而言)有关)堆上的一些 FileStream对象。

请注意C#的默认值pass-by-value语义。在这个声明中:

StaticMethod(stream);

stream字段值的副本作为参数传递给静态方法。 FileStream是引用类型,引用类型表达式的值是引用。因此,对堆上的FileStream对象的引用将传递给方法,而不是(如您所想的那样)对TestClass对象的引用/内部引用

调用FileStream时仍可以访问此GC.Collect对象的事实导致创建TestClass对象(创建它并具有通过字段引用它也可以到达。 “实时”对象通过引用它们使其他对象生效,而不是通过它们引用它们。

假设优化导致不需要的引用被“弹出”,让我们看一下创建的TestClass对象的可达性。

  • Main在调用后不需要引用它:
  • InstanceMethod,在取消引用它以读取this字段后,又不需要隐式传递的stream引用。 此时此对象符合收集条件。然后调用:
  • StaticMethod,反过来(如前所述)根本没有持有对象的引用。

因此,在读取TestClass字段后立即不需要stream对象,并且当 StaticMethod {{1}}时,它肯定有资格进行收集执行。

答案 1 :(得分:2)

这似乎是因为当TestClass的实例调用StaticMethod时,它通过引用传递流对象,而不是让StaticMethod使用this关键字来访问实例字段本身(这将保持对实例的引用活动) 。

因此,只要通过引用将其传递给静态方法,就可以对实例进行垃圾回收,并在Finalizer中处理流对象,从而引发ObjectDisposedException。

我认为Curt Nichols在博客中解释得非常好

  

为什么这会抛出?好吧,当LongRunningMethod将_input传递给Helper时,实例的最后一次实时引用丢失了。基本上我们再次从实例导出字段值,不再保留对实例的引用,允许GC完成它。 Helper留下了对已经完成的对象的引用。

答案 2 :(得分:2)

你真的没有必要防范。在上面的测试示例中,拥有对象有资格进行收集,并处置其拥有的对象。它通过该对象“外部”的事实并不意味着什么。如果您要设计一个类来销毁它声称拥有所有权的对象,请不要将该对象传递回外部,而不要通知该对象的用户在完成使用之前可能会将其销毁。

答案 3 :(得分:2)

重要的是要注意术语“垃圾收集”通常用于许多不同的过程:排队立即完成,执行终结器和(真正的)垃圾收集。具有终结器的对象或由具有终结器的对象直接或间接引用的对象永远不能用于真正的垃圾收集 - 它们只有资格被添加到立即终结队列。请注意,根据创建时使用的参数,WeakReference可能在其引用对象排队等待最终确定时失效,或者仅在“真正”垃圾收集时才会失效。

请注意,具有终结器的对象将阻止直接或间接引用的对象被垃圾收集,但不会阻止它们被最终确定。这个很重要。这意味着如果你的对象有一个终结器,当它运行时,你的类直接或间接引用的所有可终结对象通常会处于以下三种状态之一:

  1. 他们的终结器可能已经运行,在这种情况下你不需要做任何事情。
  2. 他们的终结器可能没有运行,但是一旦对象的终结器完成,它们可能会排队等待立即完成。再一次,不需要做任何事情。
  3. 如果他们尚未最终确定或排队等待最终确定,则表示他们正在使用中。试图清理它们会破坏它们。

很少有类实际上应该有终结器(有时 - 令人讨厌 - 在C#中称为析构函数)。如果一个类负责执行一些不会被其他可终结类处理的清理,那么它应该有一个终结器。如果您确实编写了一个需要终结器的类,那么在每个返回点之前让类中的每个方法或属性都包含GC.KeepAlive(this)可能是个好主意。

注意:我经常说,派生类将终结器添加到没有终结器的基类是一个非常糟糕的主意。有许多原因,但这里适用的原因是基类可能不包括正确操作所需的所有GC.KeepAlives。