Click here to Skip to main content
15,881,898 members
Articles / Programming Languages / C#
Article

Controlling object life time and garbage collection in .NET

Rate me:
Please Sign up or sign in to vote.
4.31/5 (9 votes)
2 Mar 20042 min read 95.8K   22   13
This article discusses scenarios where we have to take care of an object's life time and cannot rely on JIT's marking of objects for garbage collection.

Introduction

Managed applications and garbage collection advertise that the developer need not care about memory allocations and object lifetime, leaving them to the mercy of Garbage Collector. The JIT compiler has a mind of its own and optimizes the code under the hood. This veils the developer from the object's moment of abrogation. Although we do not know the moment of an object's destruction, careful planning and design are required to orchestrate with the Garbage Collector to avoid pitfalls.

The Problem

Guessing when and where the object is marked for garbage collection is tricky. Consider the following code :

C#
namespace NS
{
    class A
    {
        public static void Main()
        {
            System.String s;
            s = "some test string"; 
            // <- is s marked for collection after this ?
            
            System.Int32 i;
            i = 10;
        }    // <- is s marked for collection here ? 
    } 
}

The place where string s is marked for garbage collection by JIT is not known exactly. This is especially a problem when we try to control objects which are required throughout the life of an application.

Sample solution

A common example that is quoted is a mutex which is used to make sure that only one instance of the application is running. The following code illustrates this:

C#
namespace NS
{
    class SomeFormClass:System.Windows.Forms.Form
    { 
        public static void Main()
        {
            bool created;
            System.Threading.Mutex m = new 
                System.Threading.Mutex(true, "OnlyOneApp", out created);
            if (! created)
                return; 
            // <- mutex may be marked for garbage collection here ! 
            // if the application runs for long and mutex
            // is garbage collected, the design fails
            System.Windows.Forms.Application.Run(new SomeFormClass()); 
            System.GC.KeepAlive(m); 
            // <- this will keep the mutex alive till this point 
        }
    }
}

If the GC.KeepAlive() statement is missing, the JIT compiler might optimize and mark the mutex for garbage collection before the Application.Run() statement. The GC.KeepAlive() statement keeps the object alive upto that statement. In the above scenario, we know the exact place where the mutex is created and till where it is required. Another point is that the variable is accessible at both these known points. Consider an example where an object is required throughout the application but its instantiation point is unknown. Also, it is assumed that no single "live" object may be holding a reference to this required object throughout the life of the application. This scenario prompts the JIT to mark the object for garbage collection at the earliest point where the object goes out of scope. The code below illustrates a design to keep this kind of objects alive and also points out how to handle the situation in Console and Windows applications. It is most important to use the GC.SuppressFinalize() method at the end when the application exists, otherwise the application will go into an infinite loop and hangs.

C#
namespace GCDemo
{ 
    // class used to test the scenario
    class StartTest
    {
        // following delegate and event are required by console apps
        // In Windows apps, this will be taken care by ApplicationExit event
        internal delegate void ApplicationExit();
        static internal event ApplicationExit AppExit;
        public static void Main()
        {
            for (int i=0; i<3; i++)
            {
                new GCDemo(); // <- creating 3 objects here as a test case
            } 
            // <- we expect the JIT to mark for
            // garbage collect these objects atleast here
 
            // following lines are required for a console app,
            // Application.ApplicationExit event is raised by windows apps
            if (AppExit != null) 
                AppExit();
        }
    }
    // this class object should be kept alive for the entire application life
    public class GCDemo
    {
        public GCDemo()
        {
            // register with the application exit event
            // AppExit is my own event for console applications
            // See the declaration in StartTest class above
            StartTest.AppExit += new StartTest.ApplicationExit(AppExiting);

            // for windows app use the line below by 
            // implementing a EventHandler delegate
            // Application.ApplicationExit += new EventHandler(eventhandler);
        }

        ~GCDemo()
        {
            // ReRegisterForFinalize will put the entry back in the queue
            // and resurrect the object 
            System.GC.ReRegisterForFinalize(this);
        }
        void AppExiting()
        {
            // suppressing the finalization will
            // stop the application from hanging 
            System.GC.SuppressFinalize(this);
        }
    }
}

Sometimes, it is worth calling GC.Collect() and GC.WaitForPendingFinalizers() like shown below before allocating huge memory, especially when there is a crunch on memory.

C#
......
GC.Collect();
GC.WaitforPendingFinalizers();
for (int i=0; i<1000; i++)
{
        somelist.Add(new someobject());
}
......

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralYipes! Don't rely on finalization Pin
A Berglas16-Mar-04 15:03
A Berglas16-Mar-04 15:03 
GeneralRe: Yipes! Don't rely on finalization Pin
Nathan Holt at EMOM22-Mar-04 6:04
Nathan Holt at EMOM22-Mar-04 6:04 
GeneralLisp/Java/.Net is NOT C++ Pin
A Berglas22-Mar-04 12:18
A Berglas22-Mar-04 12:18 
GeneralRe: Lisp/Java/.Net is NOT C++ Pin
gnk26-Jun-07 16:12
gnk26-Jun-07 16:12 
GeneralPoint noted Pin
Sriram Chitturi10-Mar-04 6:13
Sriram Chitturi10-Mar-04 6:13 
GeneralYes, I mostly agree with zucchini. Pin
Keith Vinson10-Mar-04 5:13
Keith Vinson10-Mar-04 5:13 
GeneralRe: Yes, I mostly agree with zucchini. Pin
Sriram Chitturi11-Mar-04 3:17
Sriram Chitturi11-Mar-04 3:17 
GeneralRe: Yes, I mostly agree with zucchini. Pin
Keith Vinson11-Mar-04 5:44
Keith Vinson11-Mar-04 5:44 
GeneralRe: Yes, I mostly agree with zucchini. Pin
zucchini11-Mar-04 4:22
zucchini11-Mar-04 4:22 
Technically, it's not about scope but about extant references.

The GC can discard any object which is no longer referenced. This notion of referenced, for us humans, is bounded by scope. But technically a compiler that uses optimizer-type lookahead techniques can see this:

<br />
void myEventProcessingFunction() {<br />
  LargeObject[] allobjects = new LargeObject[10000];<br />
<br />
  for(int i = 0; i < allobjects.length; ++i) {<br />
    // use the array<br />
  }<br />
<br />
  while(true) {<br />
    // do something, like process items on a queue<br />
  }<br />
}<br />


...do you see that the compiler could release the reference of the enclosing scope to the large array after its last use, instead of technically at the closing brace? This way, for cases where you have endless loops for processing in a thread, or long waits on synchronization objects, when the GC runs it can clean up these objects that the compiler knows are never referenced again after a certain point.

In the code above that loop could run for months in a carefully written long-lived process, and retain megabytes of memory for no reason. Now in a few years of enterprise development in Java, I learned to not do these things; but here the compiler could help cover these mistakes.

Think of it another way: imagine that the compiler could see the last place where each variable is referenced in your scope, and silently inject an additional scope between the declaration and the line after that last reference?

The author is correct in that the JIT is free to "release" an object once it is no longer used, but that does not mean that:

  • the JIT actually does this (does anybody know if this is theoretical or real?)
  • you would have any way of knowing or caring!
  • it is statistically likely (except in the long-running cases I presented) that the GC would ever run while you're in the middle of some particular function (the less often it runs, the better, except on embedded devices)


quoth KeithV:

Overall, I believe you should use program structure to control how the GC will behave not by making some obscure calls into the GC....


I agree. Ordinary cases and patterns do not require manipulation of the GC, though rare cases do benefit from some of these techniques... but these are truly rare; you should not routinely be writing GC code.

Finally...
The real point is, that the GC is trustworthy. Every object that can ever be referenced again will not be thrown away by the GC. So if you can't reference the object ever again, how could you miss it when it's gone?

There too is another purpose for delegates: that the delegate object is itself strongly referenced and holds a strong reference to the callback object, so you can therefore (if good design actually permits) allow callback objects to "dangle" from core objects in the runime base class library (such as the application shutdown example).

Of course, having said this, maybe some genius out there will come up with an interesting use for a weak-referenced delegate!

If you, gentle reader, have not yet heard of weak references and are intrerested in GC issues, please go look them up. They are a useful tool to have in your toolbox (like that funny-shaped wrench in my toolbag that I haven't used since 1993)

Cheers!
- Dave G.
GeneralRe: Yes, I mostly agree with zucchini. Pin
Keith Vinson11-Mar-04 5:31
Keith Vinson11-Mar-04 5:31 
GeneralRe: Yes, I mostly agree with zucchini. Pin
Sriram Chitturi13-Mar-04 4:10
Sriram Chitturi13-Mar-04 4:10 
QuestionI'm skeptical about the last example; have you tested this? Pin
zucchini10-Mar-04 3:50
zucchini10-Mar-04 3:50 
AnswerRe: I'm skeptical about the last example; have you tested this? Pin
Sriram Chitturi11-Mar-04 3:34
Sriram Chitturi11-Mar-04 3:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.