在他的博客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
对象,因为其成员stream
被StaticMethod
引用。
是的,雷蒙德说明了
GC并不是追踪根源,而是 关于删除没有的对象 更多使用
但是,在此示例中仍然使用TestClass
对象,不是吗?
请解释一下GC在这种情况下收集TestClass
对象的权利如何?此外,更重要的是,开发人员应如何防范这些情况?
答案 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可能在其引用对象排队等待最终确定时失效,或者仅在“真正”垃圾收集时才会失效。
请注意,具有终结器的对象将阻止直接或间接引用的对象被垃圾收集,但不会阻止它们被最终确定。这个很重要。这意味着如果你的对象有一个终结器,当它运行时,你的类直接或间接引用的所有可终结对象通常会处于以下三种状态之一:
很少有类实际上应该有终结器(有时 - 令人讨厌 - 在C#中称为析构函数)。如果一个类负责执行一些不会被其他可终结类处理的清理,那么它应该有一个终结器。如果您确实编写了一个需要终结器的类,那么在每个返回点之前让类中的每个方法或属性都包含GC.KeepAlive(this)可能是个好主意。
注意:我经常说,派生类将终结器添加到没有终结器的基类是一个非常糟糕的主意。有许多原因,但这里适用的原因是基类可能不包括正确操作所需的所有GC.KeepAlives。