.NET GC Myth #1 -- Set Object to Null

.NET Garbage Collection Myth #1:

There is never any reason to setting your objects to null (or nothing in VB) since the GC will do this for you automatically.

Fact:

There are times where setting your objects to null (or nothing in VB) can make huge differences in your memory footprint.

Details:

Yes, you do not “need” to ever set an object to null since the GC will eventually collect your object after it goes out of scope.  For small objects that have a short life this is great, and its exactly why GC is a great thing.  But there are exceptions, and the experts aren't helping by ignoring these cases by simply making blanket statements.  I have an example you can download and try for yourself, as well as several charts from the example, that illustrates one of these cases -- see my last post for more details.  This particular case involves a large object that is replaced by another large object, which leaves you vulnerable to having two such large objects in memory if you don't first set the existing object to null.  Why?  Mostly because the first large object never goes out of scope until it is replaced by the second, so the GC doesn't know you're done with it early enough.  I've had some very well informed people tell me that setting the object to null in the middle of a method is meaningless -- and I believed them after hearing it so many times -- that is until I saw it for myself, thanks to Doug Ware of Magenic.  My sample app also calls the Clear method, and Dispose when its defined, but actually my more detailed tests showed they did not make any difference, but the set to null made a huge difference.  Now some people have told me this example is flawed because you could design the app in such a way so that the first large object could go out of scope before creating the second large object.  This criticism is partially valid -- my example was concocted to show the worst case, which I also don't think is all that uncommon in many winform applications with datagrids.  But I also had an earlier version of the example which did have this other design, and it also showed a significantly smaller average memory footprint when the objects were set to null, although not as drastic as the example noted above.  So what's my point?  Just that while you may not “need” to set your objects to null, there are some cases where you can realize significant gains by being preemptive and setting your large objects to null when you know they are no longer needed.

11 Comments

  • Excellent point Paul. I was laughed at when I went the null/nothing route. It seems that when I have forgotten to do this, I may have had problems. When I do go the null/nothing route, I have never had a problem. Good points Paul!



    Wally

  • After downloading your example, I noticed a flaw in your analysis.



    Setting Object To Null is usually not necessary, because the JIT compiler tracks the LAST use of a variable within a method. In such cases, the garbage collector ignores variables, when the instruction pointer exceeds the address of the last known use of the object variable.



    However, if the variable is still used further down a function, then setting object to null does help. In your example, the garbage collector has no way of knowing the variable is no longer used, because the variable is still used further down the function.

  • Let me also point out GC.KeepAlive(obj), which is a function call that is used to prevent objects from being freed prematurely.



    In the following example from Rotor, the GC can prematurely close the stream before MethodThatSpansGC() finishes. This is because the Foo object is no longer used in Main and the this pointer is no longer used when stream.MethodThatSpansGC is called.



    // class Foo {

    // Stream stream = ...;

    // protected void Finalize() { stream.Close(); }

    // void Problem() { stream.MethodThatSpansGCs(); }

    // static void Main() { new Foo().Problem(); }

    // }



  • Hi Wesner:



    Again, as I noted, my example was concocted on purpose to show the worst case. But I had an example previously that I did not upload, since it was not my point, that did not suffer from this "flaw" and which was still helped by setting objects to null, although certainly the it was not as great of an impact. Also, if you look at the graphs I made, you will see that the total committed memory even in my "flawed" example was quite frequently much more than could be explained by this "flaw" alone. So while I can't really explain it, I've seen the proof, and there certainly are situations where setting an object to null does help the GC.

  • Paul,



    I still think you are incorrect about the nulling of objects being a cure. It does work, but there are logical reasons for it, and it's not something special about null and the GC suddenly realising that it can do something different.



    If you check the managed heap usage in your test app, before and after you null out the ArrayList, you see it go from 8MB to virtually none (this is with a 1 minute run). If you check before the ArrayList.Clear(), it is around 70MB. So clearing the list brings the managed heap usage down from 70MB to 8MB. Subsequently nulling the list takes it from 8MB to ~0.



    So you might still think that using null is serving a purpose. It is, but not because null holds some special value to the GC. After the list is cleared, the internal array in the ArrayList is still allocated. At this point, there are 2 million elements in it (using ArrayList.Capacity). At 4 bytes per pointer, that's all of the 8MB which is the difference. If you call ArrayList.TrimToSize(), then all the memory is gone, and there is no benefit from nulling out the list.



    So once again it comes down to the business case. If you are going to be reloading that list back to the same size again, nulling out the list will only mean you have to spend all the time allocating the memory again. If you are finished with the list, then trimming it after emptying will do exactly the same thing as null.

  • Another note. Jerry Dennany shows that the GC works differently in depending on Debug/Release mode.

  • Yes, as far as I'm aware the optimisation for not reporting roots for locals that are still in scope, but not in use only occurs in Release mode.

  • What happens to session objects, containing, say an xmldocument object with data? I am using several objects containing from 3KB to 80KB data each....



    Memory does not seem to clear when I uses Session.Abandon... even after setting each object to nothing...

  • Keep in mind that you have to wait for the GC to come around, and any long-lived objects as session usually is will be in gen 1 or gen 2 so they won't get GCed as often. And large objects are always in gen 2, so once again you may have to wait awhile. So, when you say memory does not seem to clear, does your server have more than 32MB of memory left? If so, then you haven't reached a critical point yet, so the GC may not occur for a some time! In other words, lots of memory being allocated should not matter unless you are running very low, which is going to occur far more often if your system is running many things at once.

  • Is there any way to explicitly invoke GC to clean up the large object heap? I am in a situation where the ASPNET process kicks out when memory reaches 250MB... done all kinds of performance and memory monitoring with Microsoft, and they can't figure out what it is... crash dumps and all... so I'm contemplating working with smaller objects in string form only (serializable) and using state server. Machine.config processmodel memory limit is set at 60% of 2 Gigs RAM, so 250 Megs shouldn't be a problem.

  • Setting an object to null in the middle of a method does have its merit - JIT tries to find out when your object is not in use as much as it can but it can't always do a perfectly job.

Comments have been disabled for this content.