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?
StaticMethodis in fact not holding onto a reference theTestClassinstance’sstreammember – it’s holding onto a reference to (as far as the GC is concerned) someFileStreamobject on the heap.Note the default pass-by-value semantics of C#. In this statement:
A copy of the value of the
streamfield is passed as an argument to the static method.FileStreamis a reference-type and the value of a reference-type expression is a reference. Hence, a reference to aFileStreamobject on the heap is passed to the method, not (as you appear to be thinking) a reference / interior reference to aTestClassobjectThe fact that this
FileStreamobject is still reachable whenGC.Collectis called does not result in making theTestClassobject (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
TestClassobject.Maindoesn’t need a reference to it after it calls:InstanceMethod, which in turn doesn’t need the implicitly passedthisreference after it dereferences it to read thestreamfield. 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
TestClassobject isn’t needed right after after itsstreamfield is read, and is most certainly eligible for collection whileStaticMethodis executing.