In his blog, When does an object become available for garbage collection?, Reymond Chen writes that
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
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
.
Yes, Raymond states that
GC is not about tracing roots but about removing objects that are no more in use
However, in this example TestClass
object is still being used, isn't it?
Please explain how GC is right in collecting TestClass
object in this case? Also, more importantly, how should developers safeguard against these situations?
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.
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.
Note the default pass-by-value semantics of C#. In this statement:
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
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.
Assuming optimizations that result in unneeded references being aggressively "popped off", let's look at the reachability of the created TestClass
object.
Main
doesn't need a reference to it after it calls: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:StaticMethod
, which in turn (as mentioned earlier) isn't holding to a reference to the object at all.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.
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).
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.
I think Curt Nichols explains it pretty well in the blog
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With