So I know the basics here - an object is eligible for garbage collection when it's no longer reachable by a root (i.e. a strong reference either from a local variable in a stack frame or a static reference)
The question I have is about this potential optimization where, even if an object is referenced from a local variable, it may be garbage collected at any point in a function where the variable is no longer referenced. First - it appears that existing implementations of C# don't do this - both 2.0 and 4.0 seem to keep local references "live" until the stack frame is destroyed. But - I'd also like to write code that is still robust if and when garbage collection is optimized in later versions of the CLR.
So - without further ado, here's some code illustration:
class Foo
{
...
}
class Program
{
public static void fxn1(int blah)
{
...
}
public static void fxn2(Foo foo)
{
...
}
public static int ToInt(Foo foo)
{
...
}
public static void Main()
{
...
Foo foo = new Foo();
fxn2(foo); // I THINK foo may not be GC'ed until fxn2 returns...
// I THINK foo may be GC'ed here, even though CLR2.0 and CLR4.0 don't...
// (experiment shows CLR 2.0 and 4.0 leave foo "live" until Main returns)
fxn2(new Foo()); // I THINK the argument can't be GC'ed until fxn2 returns...
// I KNOW that even CLR2.0 and CLR4.0 will GC the argument after the return...
fxn1( ToInt(new Foo()) ); // I KNOW that new Foo is GC'able even within fxn1...
}
}
So ultimately, the rules for existing CLR's seem to be: 1. any object is "live" for the duration of a function call for which it is an immediate argument 2. any object is "live" for the duration of a function call if it is referenced by a local stack variable that is not reassigned. (even if the stack variable may not be referenced for several instructions at the end of the function)
However - apparently C# reserves the right to modify (2) so that an object is "live" up until the final use of a reference within a function.
Would this mean:
Foo foo = new Foo();
Foo foo2 = new Foo();
fxn2(foo); // foo is NOT GC'able until fxn1 returns?
// foo IS GC'able from here on? (b/c no further uses of local "foo"?)
fxn2(foo2); // foo2 is NOT GC'able within fxn2 ?
fxn1(ToInt(foo2)); // foo2 IS GC'able within fxn1 ? (existing CLR does not GC foo2)
Is there anything in the ECMA spec which deals w/ garbage collection eligibility in detail?
Well, it's impossible to give a general answer here, as when things actually become eligible for GC completely depends on your runtime's implementation.
The only thing you can trust are the guarantees - i.e., as long as an object is referenced from the stack, it won't be collected.
You cannot tell from the code when a local variable is removed from the stack, though - this is prone to compiler optimizations - in the static compiler as well as in the jitter.
So whatever may be a precise answer now may not be anymore after the next minor update of your runtime - it's usually best to write code that does not depend on such subtleties, which can only be found out by experiment, and instead relies on the runtime's guaranties only.
@M.Babcock - Thank you for the link to the ECMA spec! 8.4 was actually too general, but the answer I was looking for was in 10.9 - and is identical to Java - when a variable can be no longer referenced by any possible future code path, then it is considered eligible for garbage collection - which means that although the existing clr implementation seems to scope local variable lifetime to the stack, there's no guarantee that third party or future implementations will do so.
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