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

时间:2021-05-05 23:53:40

In his blog, When does an object become available for garbage collection?, Reymond Chen writes that

在他的博客中,什么时候一个对象可用于垃圾收集?,Reymond Chen写道

An object can become eligible for collection during execution of a method on that very object.

在该对象上执行方法期间,对象可以符合收集条件。

Also, Curt Nichols demonstrates the same point through this example

此外,Curt Nichols通过这个例子展示了同样的观点

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;
        }
    }

The output is as expected -

输出如预期 -

Ctor
InstanceMethod
Finalizer
StaticMethod

ObjectDisposedException is thrown

In this example, I am not able to understand, how GC could collect the temporary TestClass object since its member stream was being referred to by the StaticMethod.

在这个例子中,我无法理解GC如何收集临时TestClass对象,因为StaticMethod引用了它的成员流。

Yes, Raymond states that

是的,雷蒙德说

GC is not about tracing roots but about removing objects that are no more in use

GC不是追踪根,而是关于删除不再使用的对象

However, in this example TestClass object is still being used, isn't it?

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

Please explain how GC is right in collecting TestClass object in this case? Also, more importantly, how should developers safeguard against these situations?

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

4 个解决方案

#1


6  

I am not able to understand, how GC could collect the temporary TestClass object since its member stream was being referred to by the StaticMethod.

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

StaticMethod is in fact not holding onto a reference the TestClass instance's stream member - it's holding onto a reference to (as far as the GC is concerned) some FileStream object on the heap.

实际上,StaticMethod并没有保留TestClass实例的流成员的引用 - 它保持对堆上的一些FileStream对象的引用(就GC而言)。

Note the default pass-by-value semantics of C#. In this statement:

请注意C#的默认传值语义。在这个声明中:

StaticMethod(stream);

A copy of the value of the stream field is passed as an argument to the static method. FileStream is a reference-type and the value of a reference-type expression is a reference. Hence, a reference to a FileStream object on the heap is passed to the method, not (as you appear to be thinking) a reference / interior reference to a TestClass object

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

The fact that this FileStream object is still reachable when GC.Collect is called does not result in making the TestClass object (that created it and has a reference to it through a field) also reachable. "Live" objects make other objects live by referencing them, not by being referred to by them.

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

Assuming optimizations that result in unneeded references being aggressively "popped off", let's look at the reachability of the created TestClass object.

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

  • Main doesn't need a reference to it after it calls:
  • Main调用后不需要引用它:
  • InstanceMethod, which in turn doesn't need the implicitly passed this reference after it dereferences it to read the stream field. The object becomes eligible for collection at this point. It then calls:
  • InstanceMethod,在取消引用它以读取流字段后,不需要隐式传递此引用。此对象有资格进行收集。然后它调用:
  • StaticMethod, which in turn (as mentioned earlier) isn't holding to a reference to the object at all.
  • StaticMethod,反过来(如前所述)根本不保留对对象的引用。

Consequently, the TestClass object isn't needed right after after its stream field is read, and is most certainly eligible for collection while StaticMethod is executing.

因此,在读取其流字段之后不需要TestClass对象,并且在执行StaticMethod时,它肯定有资格进行收集。

#2


2  

This seems to be because when the instance of TestClass calls the StaticMethod it passes by reference the stream object, rather than letting the StaticMethod use the this keyword to access the instance field itself (which would keep a reference to the instance alive).

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

So as soon as it is passed to the static method by reference the instance can be garbage collected and the stream object disposed of in the Finalizer causing the ObjectDisposedException to be thrown.

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

I think Curt Nichols explains it pretty well in the blog

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

Why does this throw? Well, the last live reference to the instance was lost when LongRunningMethod passed _input to Helper. Essentially we've again exported the field value from the instance and no longer hold a reference to the instance, allowing the GC to finalize it. Helper is left holding a reference to an object that has been finalized.

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

#3


2  

There really is nothing you need to safeguard against. In the test example above, the owning object became eligible for collection, and disposed of an object that it had ownership of. The fact that it passed that object 'outside' means nothing. If you're going to design a class to destroy an object that it claims ownership over, don't pass it back outside without notifying users of that object that it may be destroyed before they are finished using it.

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

#4


2  

It's important to note that the term "garbage collection" is often used for a number of distinct processes: queueing for immediate finalization, executing a finalizer, and (real) garbage-collection. Objects which have finalizers, or objects that are referred to directly or indirectly by objects with finalizers, are never eligible for real garbage-collection--they're only eligible to be added to the immediate finalization queue. Note that depending upon the parameters used when it's created, a WeakReference may be invalidated either when its referent is queued for finalization, or only when it is "really" garbage-collected.

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

Note that an object with a finalizer will prevent objects which it directly or indirectly references from being garbage-collected, but it will not prevent them from being finalized. This is important. It means that if your object has a finalizer, when it runs, all finalizable objects to which your class hold direct or indirect references are generally going to be in one of three states:

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

  1. Their finalizer may already have run, in which case you don't need to do anything.
  2. 他们的终结器可能已经运行,在这种情况下你不需要做任何事情。
  3. Their finalizer may not have run, but may be queued for immediate finalization once your object's finalizer finishes. Again, no need to do anything.
  4. 他们的终结器可能没有运行,但是一旦你的对象的终结器完成,它们可能排队等待立即完成。再一次,不需要做任何事情。
  5. If they haven't been finalized or queued for finalization, they're in use. Trying to clean them up will break things.
  6. 如果它们尚未最终确定或排队等待最终确定,则它们正在使用中。试图清理它们会破坏它们。

Very few classes should actually have finalizers (sometimes--annoyingly--called destructors in C#). The only time a class should have a finalizer if it is responsible for performing some cleanup which isn't going to be handled by some other finalizable class. In the event that you do write a class which needs a finalizer, it's probably a good idea to have every method or property in the class include GC.KeepAlive(this) before each return point.

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

Note: I've often said it's a very bad idea for a derived class to add a finalizer to a base class that doesn't have one. There are a number of reasons, but one that's applicable here is that the base class might not include all the GC.KeepAlives that would be necessary for correct operation.

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

#1


6  

I am not able to understand, how GC could collect the temporary TestClass object since its member stream was being referred to by the StaticMethod.

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

StaticMethod is in fact not holding onto a reference the TestClass instance's stream member - it's holding onto a reference to (as far as the GC is concerned) some FileStream object on the heap.

实际上,StaticMethod并没有保留TestClass实例的流成员的引用 - 它保持对堆上的一些FileStream对象的引用(就GC而言)。

Note the default pass-by-value semantics of C#. In this statement:

请注意C#的默认传值语义。在这个声明中:

StaticMethod(stream);

A copy of the value of the stream field is passed as an argument to the static method. FileStream is a reference-type and the value of a reference-type expression is a reference. Hence, a reference to a FileStream object on the heap is passed to the method, not (as you appear to be thinking) a reference / interior reference to a TestClass object

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

The fact that this FileStream object is still reachable when GC.Collect is called does not result in making the TestClass object (that created it and has a reference to it through a field) also reachable. "Live" objects make other objects live by referencing them, not by being referred to by them.

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

Assuming optimizations that result in unneeded references being aggressively "popped off", let's look at the reachability of the created TestClass object.

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

  • Main doesn't need a reference to it after it calls:
  • Main调用后不需要引用它:
  • InstanceMethod, which in turn doesn't need the implicitly passed this reference after it dereferences it to read the stream field. The object becomes eligible for collection at this point. It then calls:
  • InstanceMethod,在取消引用它以读取流字段后,不需要隐式传递此引用。此对象有资格进行收集。然后它调用:
  • StaticMethod, which in turn (as mentioned earlier) isn't holding to a reference to the object at all.
  • StaticMethod,反过来(如前所述)根本不保留对对象的引用。

Consequently, the TestClass object isn't needed right after after its stream field is read, and is most certainly eligible for collection while StaticMethod is executing.

因此,在读取其流字段之后不需要TestClass对象,并且在执行StaticMethod时,它肯定有资格进行收集。

#2


2  

This seems to be because when the instance of TestClass calls the StaticMethod it passes by reference the stream object, rather than letting the StaticMethod use the this keyword to access the instance field itself (which would keep a reference to the instance alive).

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

So as soon as it is passed to the static method by reference the instance can be garbage collected and the stream object disposed of in the Finalizer causing the ObjectDisposedException to be thrown.

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

I think Curt Nichols explains it pretty well in the blog

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

Why does this throw? Well, the last live reference to the instance was lost when LongRunningMethod passed _input to Helper. Essentially we've again exported the field value from the instance and no longer hold a reference to the instance, allowing the GC to finalize it. Helper is left holding a reference to an object that has been finalized.

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

#3


2  

There really is nothing you need to safeguard against. In the test example above, the owning object became eligible for collection, and disposed of an object that it had ownership of. The fact that it passed that object 'outside' means nothing. If you're going to design a class to destroy an object that it claims ownership over, don't pass it back outside without notifying users of that object that it may be destroyed before they are finished using it.

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

#4


2  

It's important to note that the term "garbage collection" is often used for a number of distinct processes: queueing for immediate finalization, executing a finalizer, and (real) garbage-collection. Objects which have finalizers, or objects that are referred to directly or indirectly by objects with finalizers, are never eligible for real garbage-collection--they're only eligible to be added to the immediate finalization queue. Note that depending upon the parameters used when it's created, a WeakReference may be invalidated either when its referent is queued for finalization, or only when it is "really" garbage-collected.

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

Note that an object with a finalizer will prevent objects which it directly or indirectly references from being garbage-collected, but it will not prevent them from being finalized. This is important. It means that if your object has a finalizer, when it runs, all finalizable objects to which your class hold direct or indirect references are generally going to be in one of three states:

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

  1. Their finalizer may already have run, in which case you don't need to do anything.
  2. 他们的终结器可能已经运行,在这种情况下你不需要做任何事情。
  3. Their finalizer may not have run, but may be queued for immediate finalization once your object's finalizer finishes. Again, no need to do anything.
  4. 他们的终结器可能没有运行,但是一旦你的对象的终结器完成,它们可能排队等待立即完成。再一次,不需要做任何事情。
  5. If they haven't been finalized or queued for finalization, they're in use. Trying to clean them up will break things.
  6. 如果它们尚未最终确定或排队等待最终确定,则它们正在使用中。试图清理它们会破坏它们。

Very few classes should actually have finalizers (sometimes--annoyingly--called destructors in C#). The only time a class should have a finalizer if it is responsible for performing some cleanup which isn't going to be handled by some other finalizable class. In the event that you do write a class which needs a finalizer, it's probably a good idea to have every method or property in the class include GC.KeepAlive(this) before each return point.

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

Note: I've often said it's a very bad idea for a derived class to add a finalizer to a base class that doesn't have one. There are a number of reasons, but one that's applicable here is that the base class might not include all the GC.KeepAlives that would be necessary for correct operation.

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