11

Suppose I have

void foo () {
    Bar bar = new Bar(); // bar is never referred to after this line
    // (1)
    doSomethingWithoutBar();
}

At (1), is the object bar is pointing to eligible for garbage collection? Or does bar have to fall out of scope as well? Does it make a difference if GC.Collect is called by doSomethingWithoutBar?

This is relevant to know if Bar has a (C#) destructor or something funky like that.

Thomas Eding
  • 35,312
  • 13
  • 75
  • 106
  • 2
    I am just gonna wait for Eric Lippert to swing by here and take care of this one... – Stefan H Jul 13 '12 at 21:57
  • @StefanH, I think I've seen an answer from Eric on a question very similar to this (or a blog entry from him) but my google-fu is failing right now =( – Rob Jul 13 '12 at 22:02
  • @Rob - I know! I tried just googling "C# Garbage Collection" because I assumed he would be the first result. – Stefan H Jul 13 '12 at 22:06
  • FYI, common practice in these situations (or more commonly, when an unmanaged resource needs to stay alive), is to use [GC.KeepAlive()](http://msdn.microsoft.com/en-ca/library/system.gc.keepalive.aspx). – Rei Miyasaka Jul 26 '12 at 12:49

5 Answers5

8

Objects can become eligible for garbage collection as soon as it's certain that they will no longer be used. It's entirely possible that bar will be garbage collected before the variable goes out of scope.

Proof:

using System;

class Bar
{
    ~Bar() { Console.WriteLine("Finalized!"); }
}

class Program
{
    static void Main(string[] args)
    {
        Bar bar = new Bar();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Press any key to exit...");
        Console.ReadLine();
    }
}

Run in Release Mode (because it doesn't get collected in Debug Mode).

Output:

Finalized!
Press any key to exit...

It also works on ideone which uses Mono. The output is the same.

Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • I think it will wait for scope complete, to ensure that there is no any *runtime* reference to that local object. – Tigran Jul 13 '12 at 22:01
  • @Tigran, as it's a local variable , how could there be any reference at *runtime* to it? =) – Rob Jul 13 '12 at 22:03
  • I could not reproduce this situation. In any version of the framework: 2, 3.0, 3.5, 4.0. – Hailton Jul 13 '12 at 22:39
  • 1
    @Hailton: Did you try running it in Release Mode? I tested it using Visual Studio C# 2010 Express (C# 4) in release mode. You can click the ideone link and see the output - it works there too. I've also simplified the code somewhat. You can just copy and paste it into a new console project, switch to Release mode, then press F5. I used these instructions to change Visual Studio Express to build in Release mode: http://stackoverflow.com/a/3127008/61974 – Mark Byers Jul 13 '12 at 22:45
  • Yes, you're right. In release mode I was able to reproduce the situation. Thanks for the clarification. I got a question: I made a similar code but can not do the GC to collect objects. I'll post as a new question with this code. – Hailton Jul 13 '12 at 22:58
  • @Rob: it's enough that `local` variable assign to some `global` variable *before* exiting the scope. – Tigran Jul 14 '12 at 07:32
  • @Tigran, true, but that doesn't happen *in the example in this question*, hence your original comment is incorrect in this context - which is what I meant =) – Rob Jul 14 '12 at 11:26
3

From a quick reading of the spec, it looks like it's implementation specific. It's allowed to garbage collect it, but not required to.

I get this from a note in section 10.9 "Automatic Memory Management" of the ECMA Spec:

[Note: Implementations might choose to analyze code to determine which references to an object can be used in the future. For instance, if a local variable that is in scope is the only existing reference to an object, but that local variable is never referred to in any possible continuation of execution from the current execution point in the procedure, an implementation might (but is not required to) treat the object as no longer in use. end note]

Emphasis mine.

Paul Phillips
  • 6,093
  • 24
  • 34
1

Without defining what version of the CLR you're referring to, it's impossibledifficult to be definitive about the behaviour that you'll see here.

A hypothetical CLR could, in this example, assuming that the following is true:

  1. The constructor for Bar does nothing
  2. There are no fields that are initialised (i.e. there are no potential side-effects to the objects construction)

Entirely disregard the line Bar bar = new Bar(); and optimise it away as it "does nothing".

As far as my memory serves, in current versions of the CLR bar is eligible for garbage collection the moment after you've constructed it.

Rob
  • 45,296
  • 24
  • 122
  • 150
  • I think you mean a "hypothetical compiler" – Stefan H Jul 13 '12 at 22:08
  • @StefanH, if by "hypothetical compiler" you mean "hypothetical **JIT** compiler", then yes, that's what I mean =) Otherwise `Bar` could be defined in a different assembly which could be swapped out post-`csc` compile, resulting in `Bar` at compile time being stripped out, even though `Bar` at runtime would have done something.. (think add-in development as a possible scenario) – Rob Jul 13 '12 at 22:13
  • I was more thinking that it would be stripped out before the CLR is created, which is at compile time, right? EDIT: Nope, I'm just wrong (CLR does not mean what I thought it meant), I was thinking it might get stripped out before the CIL is generated. – Stefan H Jul 13 '12 at 22:14
  • CLR = Common Language Runtime, i.e. the ".net Virtual Machine" that runs the MSIL/CIL that the csc/vbc compilers produce. In my hypothetical scenario, `Bar` could be defined in a different assembly, so the compiler can't strip it out as it can't be sure that the assembly containing `Bar` at runtime will have the same implementation of `Bar`. Only the JIT compiler can know that =) – Rob Jul 13 '12 at 22:22
  • Thanks, that makes a lot of sense. I appreciate the explanation. In the future I may very well be saying "I'll just wait for Rob to come be here and take care of this one..." – Stefan H Jul 13 '12 at 22:25
1

Marc answered the question, but here is the solution:

void foo () {
    Bar bar = new Bar(); // bar is never referred to after this line
    // (1)
    doSomethingWithoutBar();

    GC.KeepAlive(bar); // At the point where you no longer need it
}
Tergiver
  • 14,171
  • 3
  • 41
  • 68
1

This can definitely occur. For instance, here is a demonstration that an instance can be finalized while you are still executing its constructor:

class Program
{
    private static int _lifeState;
    private static bool _end;

    private sealed class Schrodinger
    {
        private int _x;

        public Schrodinger()
        {
            //Here I'm using 'this'
            _x = 1;

            //But now I no longer reference 'this'
            _lifeState = 1;

            //Keep busy to provide an opportunity for GC to collect me
            for (int i=0;i<10000; i++)
            {
                var garbage = new char[20000];
            }

            //Did I die before I finished being constructed?
            if (Interlocked.CompareExchange(ref _lifeState, 0, 1) == 2)
            {
                Console.WriteLine("Am I dead or alive?");
                _end = true;
            }
        }

        ~Schrodinger()
        {
            _lifeState = 2;
        }
    }

    static void Main(string[] args)
    {
        //Keep the GC churning away at finalization to demonstrate the case
        Task.Factory.StartNew(() =>
            {
                while (!_end)
                {
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                }
            });

        //Keep constructing cats until we find the desired case
        int catCount = 0;
        while (!_end)
        {
            catCount++;
            var cat = new Schrodinger();
            while (_lifeState != 2)
            {
                Thread.Yield();
            }
        }
        Console.WriteLine("{0} cats died in the making of this boundary case", catCount);
        Console.ReadKey();
    }
}

In order for this to work, you need to emit a Release build and run it outside Visual Studio (as otherwise the debugger inserts code that prevents the effect.) I've tested this with VS 2010 targetting .NET 4.0 x64.

You can tweak the iterations on the 'keep busy' loop to impact the probability of the Cat winding up finalized before its complete construction.

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102