开发者

is there any benefit to nulling references to "encourage" .NET garbage collection?

Suppose there is the following code:

foreach (...)
{
    List<int> localList 开发者_JAVA百科= new List<int>(100);

    // do stuff

    localList = null;
}

From time to time, I am tempted to null references just as the procedure is about to exit (that is, return) or in this case, just as it is about to loop. Is there any benefit to doing so, even if a small one?


There is no benefit to doing this for local variables. The CLR knows, by means of the JIT, exactly when a local variable is no longer used within a method and hence collectable.

Raymond Chen recently did a very in depth blog article on exactly when objects are collectible . It covers this scenario in detail and is worth the read

  • http://blogs.msdn.com/b/oldnewthing/archive/2010/08/10/10048149.aspx

There is however a couple of exceptions to this rule. If the local variable in question is captured into a closure or an iterator then yes nulling out the variable has an effect. Namely because the local is no longer a local but instead is a field and has different GC semantics.


No. In fact, "nulling" local variables can, under some circumstances, prevent garbage collection. In normal operation, as soon as a variable is no longer reachable from any executing code, it becomes available for garbage collection. If, at the end of your method, you "null-out" the variable, you keep it "alive" until that moment, and actually delay it's availability for garbage collection.


Not if the declaration is within blocks like you have. Once it's out of scope the reference will be nullified automatically and GC'd.


I wrote a long and exhaustive blog article answering this question.

To summarize, the general answer is "no; there is no benefit". However, there are a few special cases:

  • Static fields should be set to null when they are no longer needed - unless the process is shutting down, in which case setting static fields to null is unnecessary.
  • Local variables hardly ever need to be set to null. There is only one exception: It may be beneficial to set local variables to null if running on a non-Microsoft CLR.
  • Instance fields hardly ever need to be set to null. There is only one exception: An instance field may be set to null if the referencing object is expected to outlive the referenced object. [Note that the semi-common practice of setting instance fields to null in IDisposable.Dispose does not meet this test, and should not be encouraged].

In conclusion: generally speaking, setting variables to null to help the garbage collector is not recommended. If it is deemed necessary, then an unusual condition exists and it should be carefully documented in the code.


If a class has a finalizer, any non-null object-reference fields will cause the referred-to objects to be held longer than they otherwise would. If the objects are known to be unnecessary even before the finalizer runs, clearing out the object-reference fields will allow the objects to be collected one "generation" sooner than they otherwise would be. That can be a big win.

If the lifetime (useful or not) of an object is expected to outlive the useful lifetime of an object to which it has a reference, needlessly holding a reference will prevent the latter object from being collected until the former is (i.e. the reference will force the latter object to be kept even after it has become useless). Clearing the reference will avoid that problem.

If a class written in vb.net has any "WithEvents variables", they should be cleared out (set them to nothing) any time the object holding them becomes useless. A class cannot be garbage-collected while it holds a reference to a live object in a "WithEvents variable". If e.g. an enumerator holds a "WithEvents" reference to an underlying collection (e.g. so it can receive events if/when the collection is changed) and its Dispose handler does not clear its reference to the underlying collection, the enumerator will be kept alive as long as the underlying collection is. If the collection is enumerated very many times, this could be a massive memory leak.


No. Let the GC do its job and only help it if you need to.


There is one important case where nulling has an effect, which is when you are using a debug build with some jit optimisations turned off. A lot of the funkier behaviour as to when objects are cleaned up can stop, to make debugging easier.

It's important, because it can lead to a false view of what is happening in the release build.


And the response is: yes if you know what you are doing! But it's premature optimization (the root of all evils for many persons), so you shouldn't do it unless you really need the memory.

Now, I'll analyze only two cases where it could be useful.

  • Properties of an object that you know won't be referenced anymore (for example you cached some complex calculation in a private property. You know you won't need anymore, you clean it) But this one was said by many persons

  • Fields in complex methods. This one is the opposite of what others have said, but I have an example, so I know I'm right :-) Now, normally, in a sequential method, the GC can see where a local reference is not used anymore (technically the important part is when it's not read anymore. And calling methods of the variable is "reading". Writing to a variable doesn't count, because if no one will read the new value, it's useless to write it)

But I have said sequential method. And non-sequential methods? Let's try! (clearly I have already done it! I won't make an ideone example because the GC of mono is different than the GC of .NET).

Compile this piece of code in Release mode and lauch without the debugger (so CTRL-F5) and observe the results:

using System;

namespace ConsoleApplication17
{
    class Program
    {
        static void Main()
        {
            const int cycles = 1000000;

            {
                string str = new string(' ', 10000000);

                Console.WriteLine("Let's see when the GC will free the memory");
                Console.WriteLine("Start: {0}", GC.GetTotalMemory(true));

                for (int i = 0; i < 1000000; i++)
                {
                    if (i == cycles - 1)
                    {
                        Console.WriteLine("Near end: {0}", GC.GetTotalMemory(true));
                    }

                    //Here we reference the string, 
                    //but only in the first 100 iterations
                    if (i < 100 && str[str.Length - 1] == 'c')
                    {
                        throw new Exception();
                    }
                }

                Console.WriteLine("End: {0}", GC.GetTotalMemory(true));
            }

            Console.WriteLine();

            {
                string str = new string(' ', 10000000);

                Console.WriteLine("Let's do the work for him");
                Console.WriteLine("Start: {0}", GC.GetTotalMemory(true));

                for (int i = 0; i < 1000000; i++)
                {
                    if (i == cycles - 1)
                    {
                        Console.WriteLine("Near end: {0}", GC.GetTotalMemory(true));
                    }

                    //Here we reference the string, 
                    //but only in the first 100 iterations
                    if (i < 100 && str[str.Length - 1] == 'c')
                    {
                        throw new Exception();
                    }
                    else if (i == 100)
                    {
                        str = null;
                        Console.WriteLine("Just nullified the string: {0}", GC.GetTotalMemory(true));
                    }
                }

                Console.WriteLine("End: {0}", GC.GetTotalMemory(true));
            }

            Console.ReadKey();
        }
    }
}

The results:

Let's see when the GC will free the memory
Start: 20042264
Near end: 20042888
End: 42872

Let's do the work for him
Start: 20042888
Just nullified the string: 42872
Near end: 42872
End: 42872

An explanation: this program is an example of cyclic program where a big object is initialized outside of the cycle (a for cycle) and the big object is used only in part of the cycle (for example in the first 100 iterations). Clearly the GC can't easily see that from the end of iteration 99 (the 100th iteration, 0-99) onward the object won't be referenced.

But remember the words premature optimization and root of all evils! :-)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜