【问题标题】:Need explanation of how an object can get garbage-collected when a method is executing on it需要解释在对象上执行方法时如何进行垃圾收集
【发布时间】:2011-07-11 00:06:44
【问题描述】:

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

一个对象可以有资格 在执行期间收集 那个对象上的方法。

另外,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 对象时是如何正确的?此外,更重要的是,开发人员应如何防范这些情况?

【问题讨论】:

  • 即使使用 release 构建,我也无法重现它。
  • @Aliostad - 不要在 Visual Studio 中按 F5。在Release模式下编译并双击生成的exe。

标签: .net garbage-collection


【解决方案1】:

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

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

注意 C# 的默认值传递语义。在此声明中:

StaticMethod(stream);

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

GC.Collect 被调用时这个FileStream 对象仍然可以访问的事实不会 导致创建TestClass 对象(创建它并通过字段引用它) 也可以到达。 “活动”对象通过引用它们而不是被它们引用来使其他对象活动。

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

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

因此,TestClass 对象在其stream 字段被读取后就不需要了,并且在StaticMethod 正在执行时最肯定有资格进行收集

【讨论】:

    【解决方案2】:

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

    因此,一旦它通过引用传递给静态方法,就可以对实例进行垃圾收集,并且在 Finalizer 中处理流对象,从而引发 ObjectDisposedException。

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

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

    【讨论】:

      【解决方案3】:

      您确实没有什么需要防范的。在上面的测试示例中,拥有对象变得有资格被收集,并处置了它拥有的对象。它通过该对象“外部”这一事实毫无意义。如果您要设计一个类来销毁它声称拥有所有权的对象,请不要在没有通知用户该对象可能在他们使用完毕之前将其销毁的情况下将其传回外部。

      【讨论】:

        【解决方案4】:

        请务必注意,术语“垃圾收集”通常用于许多不同的进程:立即终结排队、执行终结器和(真正的)垃圾收集。具有终结器的对象,或由具有终结器的对象直接或间接引用的对象,永远不符合真正的垃圾收集条件——它们只能被添加到立即终结队列中。请注意,根据创建时使用的参数,WeakReference 可能会在其所指对象排队等待完成时或仅当它“真正”被垃圾回收时失效。

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

        1. 他们的终结器可能已经运行,在这种情况下,您无需执行任何操作。
        2. 他们的终结器可能尚未运行,但可能会在您的对象的终结器完成后排队等待立即终结。再次,无需做任何事情。
        3. 如果它们尚未最终确定或排队等待最终确定,则它们正在使用中。试图清理它们会破坏事物。

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-12-16
          • 2011-07-16
          • 1970-01-01
          • 2012-03-08
          • 2012-04-18
          • 1970-01-01
          • 2012-03-26
          • 2019-09-04
          相关资源
          最近更新 更多