Is Correct to Use Gc.Collect(); Gc.Waitforpendingfinalizers();

Is correct to use GC.Collect(); GC.WaitForPendingFinalizers();?

The short answer is: take it out. That code will almost never improve performance, or long-term memory use.

All your points are true. (It can generate a deadlock; that does not mean it always will.) Calling GC.Collect() will collect the memory of all GC generations. This does two things.

  • It collects across all generations every time - instead of what the GC will do by default, which is to only collect a generation when it is full. Typical use will see Gen0 collecting (roughly) ten times as often than Gen1, which in turn collects (roughly) ten times as often as Gen2. This code will collect all generations every time. Gen0 collection is typically sub-100ms; Gen2 can be much longer.
  • It promotes non-collectable objects to the next generation. That is, every time you force a collection and you still have a reference to some object, that object will be promoted to the subsequent generation. Typically this will happen relatively rarely, but code such as the below will force this far more often:

    void SomeMethod()
    {
    object o1 = new Object();
    object o2 = new Object();

    o1.ToString();
    GC.Collect(); // this forces o2 into Gen1, because it's still referenced
    o2.ToString();
    }

Without a GC.Collect(), both of these items will be collected at the next opportunity. With the collection as writte, o2 will end up in Gen1 - which means an automated Gen0 collection won't release that memory.

It's also worth noting an even bigger horror: in DEBUG mode, the GC functions differently and won't reclaim any variable that is still in scope (even if it's not used later in the current method). So in DEBUG mode, the code above wouldn't even collect o1 when calling GC.Collect, and so both o1 and o2 will be promoted. This could lead to some very erratic and unexpected memory usage when debugging code. (Articles such as this highlight this behaviour.)

EDIT: Having just tested this behaviour, some real irony: if you have a method something like this:

void CleanUp(Thing someObject)
{
someObject.TidyUp();
someObject = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}

... then it will explicitly NOT release the memory of someObject, even in RELEASE mode: it'll promote it into the next GC generation.

Does the Garbage Collector benefit from multiple calls to Collect and WaitForPendingFinalizers()?

This from the example program on the Microsoft documentation site.

   //Force garbage collection.
GC.Collect();

// Wait for all finalizers to complete before continuing.
// Without this call to GC.WaitForPendingFinalizers,
// the worker loop below might execute at the same time
// as the finalizers.
// With this call, the worker loop executes only after
// all finalizers have been called.
GC.WaitForPendingFinalizers();

When the garbage collector finds objects that can be reclaimed, it checks each object to determine the object's finalization requirements. If an object implements a finalizer and has not disabled finalization by calling SuppressFinalize, the object is placed in a list of objects that are marked as ready for finalization. The garbage collector calls the Finalize methods for the objects in this list and removes the entries from the list. This method blocks until all finalizers have run to completion.


The thread on which finalizers are run is unspecified, so there is no guarantee that this method will terminate. However, this thread can be interrupted by another thread while the WaitForPendingFinalizers method is in progress. For example, you can start another thread that waits for a period of time and then interrupts this thread if this thread is still suspended.

From what I can tell there's no clear indication of a benefit of calling it twice.

WaitForFullGCComplete vs (WaitForPendingFinalizers + collect)?

Unless I misunderstood, both the usages in the link you've pointed out is wrong. As you quoted from CLR via C#, both WaitForFullGCApproach and WaitForFullGCComplete should be used in pairs.

WaitForFullGCComplete is very different than Collect + WaitForPendingFinalizers. Its usage is completely different.

Lets say you're writing a high performance, low latency server application which handles several thousands of requests per second. Obviously the server will allocate considerable amount of memory which can trigger a full gc(blocking!) which is a bad news for us.

Yes full gc will take a while to complete, till then you can't process the requests from clients (as your threads will be suspended). It isn't desirable for a high performance application to tolerate a full gc at peak hours.

In such case, if you want to redirect the further requests to some other server which can handle them, you'd use GC.RegisterForFullGCNotification, GC.WaitForFullGCApproach and GC.WaitForFullGCComplete methods respectively.

WaitForFullGCApproach will block till the GC decides to do a full gc, it is a notification for you to proactively redirect the requests or take some action.

Then WaitForFullGCComplete will block till the full gc is completed. It is a notification for you to start processing the requests again.

GC.RegisterForFullGCNotification docs provides a good example and explains it better.

Will GC.Collect() + GC.WaitForPendingFinalizers() ever NOT reclaim all reclaimable memory?

In general, this should clean up most things.

However, if you have code in your finalizers, it's possible that you will need to call GC.Collect() twice, as the first time will cause the finalizers to execute, but the actual memory cannot be cleaned until after the finalizer has completed, which means the subsequent call will catch the object.

What's so wrong about using GC.Collect()?

From Rico's Blog...

Rule #1

Don't.

This is really the most important
rule. It's fair to say that most
usages of GC.Collect() are a bad idea
and I went into that in some detail in
the orginal posting so I won't repeat
all that here. So let's move on to...

Rule #2

Consider calling GC.Collect() if some
non-recurring event has just happened
and this event is highly likely to
have caused a lot of old objects to
die.

A classic example of this is if you're
writing a client application and you
display a very large and complicated
form that has a lot of data associated
with it. Your user has just
interacted with this form potentially
creating some large objects... things
like XML documents, or a large DataSet
or two. When the form closes these
objects are dead and so GC.Collect()
will reclaim the memory associated
with them...

So it sounds like this situation may fall under Rule #2, you know that there's a moment in time where a lot of old objects have died, and it's non-recurring. However, don't forget Rico's parting words.

Rule #1 should trump Rule #2 without
strong evidence.

Measure, measure, measure.

Finalizer not called after GC.WaitForPendingFinalizers

Where did I go wrong here?

Short version:

The important thing to realize here is that C# is not like C++ where } means "we must run destructors of locals now".

C# is allowed to increase the lifetime of any local variable at its discretion, and it does so all the time. Therefore you should never expect a local variable's contents to be collected just because control enters a region where the variable is out of scope.

Long version:

The rule that { } defines a nested local variable declaration space is a rule of the C# language; it is emphatically NOT a feature of the IL that C# is compiled to!

Those local variable declaration spaces are there so that you can organize your code better and so that the C# compiler can find bugs where you used a local variable when it was not in scope. But C# does NOT emit IL that says "the following local variables are now out of scope" at the bottom of your { } block.

The jitter is allowed to notice that your second myClass is never read from and therefore could be removed from the root set after the final write. But the fact that it is allowed to do so does not require it to do so, and typically it will not.

That "typically" is doing some heavy lifting there because, of course, the jitter is allowed to shorten the lifetime of a local. Consider this bad situation:

{
ManagedHandle handle = NativeObject.Open();
SomeNativeCode.CacheTheUnderlyingHandle(handle);
// handle is still in scope but the jitter is allowed to
// realize it is never read again at this point and remove the
// local from the root set. If you have the misfortune that you
// get a GC and finalize right now then the resource is deallocated
// by the time we get to:
SomeNativeCode.UseTheCachedHandle();
}

If you're in this unfortunate situation, use KeepAlive to force a local to stay alive.

Under what circumstances, we need to call GC.Collect twice

Sounds like you have something with a finalizer, basically - if you only call GC.Collect() once, the finalizers are finishing but the finalized objects aren't being collected.

Whether or not that represents a bug is a different matter. Generally it's not a good idea for there to be finalizers which actually need to be executing, but maybe it's okay in your case.

Finilize and GC.Collect

In the first example:

var h = new Haha()

the variable isn't referenced anywhere, so it is removed. In this way the Haha() isn't referenced anywhere just after being created and so can be GC at any time.

In the second example the variable h is referenced, so it has a "maximum" lifetime of the block in which it is referenced (so the whole main). The compiler and the GC could detect the "last" use of h and consider it to be unreferenced just after the Console.WriteLine (the last use of h) (shortening its lifetime), but clearly don't do so.

Note that if you run your program in Release mode + Run without Debugger, the Code 2 will give (at least it gives to me) the same result as Code 1. The debugger, to make it easier to debug, guarantee that all the references are valid until the end of the block where they are declared (see https://stackoverflow.com/a/7165380/613130) Running the program in Release mode + Run without Debugger we solve this.

Done some tests: to have the shorter lifetime you need to have (at the same time): Debug Info (Properties->Build->Advanced->Debug Info) at none or pdb-only, plus run the program without debugger (Ctrl+F5). If you set the Debug Info at Full, then a DebuggableAttribute is injected in the assembly, and the CLR won't try to shorten the lifetime of the variables. The same if you run with a debugger. If you compile by command line, setting the option /debug is equivalent to /debug:full (see https://msdn.microsoft.com/en-us/library/8cw0bt21.aspx)



Related Topics



Leave a reply



Submit