Best Practice for Forcing Garbage Collection in C#

Viewed 159391

In my experience it seems that most people will tell you that it is unwise to force a garbage collection but in some cases where you are working with large objects that don't always get collected in the 0 generation but where memory is an issue, is it ok to force the collect? Is there a best practice out there for doing so?

17 Answers

The best practise is to not force a garbage collection.

According to MSDN:

"It is possible to force garbage collection by calling Collect, but most of the time, this should be avoided because it may create performance issues. "

However, if you can reliably test your code to confirm that calling Collect() won't have a negative impact then go ahead...

Just try to make sure objects are cleaned up when you no longer need them. If you have custom objects, look at using the "using statement" and the IDisposable interface.

This link has some good practical advice with regards to freeing up memory / garbage collection etc:

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

Look at it this way - is it more efficient to throw out the kitchen garbage when the garbage can is at 10% or let it fill up before taking it out?

By not letting it fill up, you are wasting your time walking to and from the garbage bin outside. This analogous to what happens when the GC thread runs - all the managed threads are suspended while it is running. And If I am not mistaken, the GC thread can be shared among multiple AppDomains, so garbage collection affects all of them.

Of course, you might encounter a situation where you won't be adding anything to the garbage can anytime soon - say, if you're going to take a vacation. Then, it would be a good idea to throw out the trash before going out.

This MIGHT be one time that forcing a GC can help - if your program idles, the memory in use is not garbage-collected because there are no allocations.

I think the example given by Rico Mariani was good: it may be appropriate to trigger a GC if there is a significant change in the application's state. For example, in a document editor it may be OK to trigger a GC when a document is closed.

There are few general guidelines in programming that are absolute. Half the time, when somebody says 'you're doing it wrong', they're just spouting a certain amount of dogma. In C, it used to be fear of things like self-modifying code or threads, in GC languages it is forcing the GC or alternatively preventing the GC from running.

As is the case with most guidelines and good rules of thumb (and good design practices), there are rare occasions where it does make sense to work around the established norm. You do have to be very sure you understand the case, that your case really requires the abrogation of common practice, and that you understand the risks and side-effects you can cause. But there are such cases.

Programming problems are widely varied and require a flexible approach. I have seen cases where it makes sense to block GC in garbage collected languages and places where it makes sense to trigger it rather than waiting for it to occur naturally. 95% of the time, either of these would be a signpost of not having approached the problem right. But 1 time in 20, there probably is a valid case to be made for it.

I've learned to not try to outsmart the garbage collection. With that said, I just stick to using using keyword when dealing with unmanaged resources like file I/O or database connections.

Not sure if it is a best practice, but when working with large amounts of images in a loop (i.e. creating and disposing a lot of Graphics/Image/Bitmap objects), i regularly let the GC.Collect.

I think I read somewhere that the GC only runs when the program is (mostly) idle, and not in the middle of a intensive loop, so that could look like an area where manual GC could make sense.

I think you already listed the best practice and that is NOT to use it unless REALLY necessary. I would strongly recommend looking at your code in more detail, using profiling tools potentially if needed to answer these questions first.

  1. Do you have something in your code that is declaring items at a larger scope than needed
  2. Is the memory usage really too high
  3. Compare performance before and after using GC.Collect() to see if it really helps.

Suppose your program doesn't have memory leakage, objects accumulates and cannot be GC-ed in Gen 0 because: 1) They are referenced for long time so get into Gen1 & Gen2; 2) They are large objects (>80K) so get into LOH (Large Object Heap). And LOH doesn't do compacting as in Gen0, Gen1 & Gen2.

Check the performance counter of ".NET Memory" can you can see that the 1) problem is really not a problem. Generally, every 10 Gen0 GC will trigger 1 Gen1 GC, and every 10 Gen1 GC will trigger 1 Gen2 GC. Theoretically, GC1 & GC2 can never be GC-ed if there is no pressure on GC0 (if the program memory usage is really wired). It never happens to me.

For problem 2), you can check ".NET Memory" performance counter to verify whether LOH is getting bloated. If it is really a issue to your problem, perhaps you can create a large-object-pool as this blog suggests http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx.

Large objects are allocated on LOH (large object heap), not on gen 0. If you're saying that they don't get garbage-collected with gen 0, you're right. I believe they are collected only when the full GC cycle (generations 0, 1 and 2) happens.

That being said, I believe on the other side GC will adjust and collect memory more aggressively when you work with large objects and the memory pressure is going up.

It is hard to say whether to collect or not and in which circumstances. I used to do GC.Collect() after disposing of dialog windows/forms with numerous controls etc. (because by the time the form and its controls end up in gen 2 due to creating many instances of business objects/loading much data - no large objects obviously), but actually didn't notice any positive or negative effects in the long term by doing so.

One more thing, triggering GC Collect explicitly may NOT improve your program's performance. It is quite possible to make it worse.

The .NET GC is well designed and tuned to be adaptive, which means it can adjust GC0/1/2 threshold according to the "habit" of your program memory usage. So, it will be adapted to your program after some time running. Once you invoke GC.Collect explicitly, the thresholds will be reset! And the .NET has to spent time to adapt to your program's "habit" again.

My suggestion is always trust .NET GC. Any memory problem surfaces, check ".NET Memory" performance counter and diagnose my own code.

However, if you can reliably test your code to confirm that calling Collect() won't have a negative impact then go ahead...

IMHO, this is similar to saying "If you can prove that your program will never have any bugs in the future, then go ahead..."

In all seriousness, forcing the GC is useful for debugging/testing purposes. If you feel like you need to do it at any other times, then either you are mistaken, or your program has been built wrong. Either way, the solution is not forcing the GC...

There are some scenarios where there will definitely be very little to no negative impact on your system when forcing a garbage collection e.g. On a date roll/a scheduled time where the system is not in use.

Aside from such times you would need to test performance of your code before and after implementing the forced collect to ensure that it is actually beneficial.

I do NOT recommend manual garbage collection. I assure you that you're not disposing of large objects properly. Make use of the USING statement. Whenever you instantiate an object, be sure to DISPOSE of it when you are through using it. This sample code creates a connection with USING statements. Then it instantiates a shipping label object, uses it, and disposes of it properly.

         Using con As SqlConnection = New SqlConnection(DB_CONNECTION_STRING)
            con.Open()

            Using command As SqlCommand = New SqlCommand(sqlStr, con)
                Using reader As SqlDataReader = command.ExecuteReader()

                    While reader.Read()
                        code_here()
                    End While
                End Using
            End Using
        End Using
        Dim f1 As frmShippingLabel
        f1 = New frmShippingLabel
        f1.PrintLabel()
        f1.Dispose()
Related