Best Practice for Forcing Garbage Collection in C#
Asked Answered
B

17

125

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?

Brisance answered 24/10, 2008 at 13:49 Comment(0)
F
118

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

Firmin answered 24/10, 2008 at 14:1 Comment(3)
Also, you can set different LatencyMode's msdn.microsoft.com/en-us/library/bb384202.aspxSternwheeler
If your objects point to unmanaged memory, you can let the garbage collector know through the GC.AddMemoryPressure Api (msdn.microsoft.com/en-us/library/…). This gives the garbage collector more information about your system without interfering with the collection algorithms.Deciduous
+1: * look at using the "using statement" and the IDisposable interface.* I wouldn't even consider forcing except as a last resort - good advice (read as 'disclaimer'). I am, however, forcing collection in a unit test to simulate losing an active reference in a back-end operation - ultimately throwing a TargetOfInvocationNullException.Bountiful
D
36

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.

Drapery answered 2/11, 2008 at 2:33 Comment(3)
If you had a baby that would die if you left it for more than a minute, and you only ever had a minute to handle the garbage, then you'd want to do a little each time instead of all at once. Unfortunately, the GC::Collect() method isn't any quicker the more often you call it. So for a realtime engine, if you can't just use the dispose mechanism and let the GC pool your data, then you shouldn't use a managed system -- as per my answer (probably below this one, lol).Aguste
In my case, I'm running a A* (shortest path) algorith REPEATEDLY to performance tune it... which, in production, will only be run once (on each "map"). So I want the GC to be done before each iteration, outside of my "performance measured block", because I feel that more closely models the situation in production, in which the GC could/should be forced after navigating each "map".Virgiliovirgin
Calling GC before the measured block actually does not models the situation in production, because in production the GC will be done in unpredictable times. To mitigate that, you should take a long measurement that will include several GC executions, and factor the peaks during GC into your analysis and statistics.Cambyses
D
35

The best practise is to not force a garbage collection in most cases. (Every system I have worked on that had forced garbage collections, had underlining problems that if solved would have removed the need to forced the garbage collection, and sped the system up greatly.)

There are a few cases when you know more about memory usage then the garbage collector does. This is unlikely to be true in a multi user application, or a service that is responding to more then one request at a time.

However in some batch type processing you do know more then the GC. E.g. consider an application that.

  • Is given a list of file names on the command line
  • Processes a single file then write the result out to a results file.
  • While processing the file, creates a lot of interlinked objects that can not be collected until the processing of the file have complete (e.g. a parse tree)
  • Does not keep much state between the files it has processed.

You may be able to make a case (after careful) testing that you should force a full garbage collection after you have process each file.

Another cases is a service that wakes up every few minutes to process some items, and does not keep any state while it’s asleep. Then forcing a full collection just before going to sleep may be worthwhile.

The only time I would consider forcing a collection is when I know that a lot of object had been created recently and very few objects are currently referenced.

I would rather have a garbage collection API when I could give it hints about this type of thing without having to force a GC my self.

See also "Rico Mariani's Performance Tidbits"

Deanery answered 24/9, 2009 at 15:32 Comment(2)
Analogy: Playschool (system) keeps crayons (resources). Depending on number of kids (tasks) and scarcity of colours, teacher (.Net) decides how to allocate and share among kids. When a rare colour is requested, teacher can allocate from pool or look for one that isn't being used. Teacher has discretion to periodically gather unused crayons (garbage collect) to keep things tidy (optimise resource use). Generally a parent (programmer) cannot predetermine best classroom crayon tidying policy. One child's scheduled nap is unlikely to be a good moment to interfere with the other kids' colouring.Cityscape
@AlanK, I like this, as when the kids go home for the day, its a very good time for the teachers helper to do a good tidy up without the kids gettting in the way. (Recent systems I have worked on have just restarted the service process at such times instread of forceing GC.)Deanery
R
22

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.

Rodge answered 2/11, 2008 at 2:51 Comment(1)
Or just before opening a large contiguous object that has shown a history of failure and presents no efficient resolution for increasing its granularity.Vile
H
18

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.

Hammonds answered 6/11, 2008 at 4:25 Comment(0)
A
12

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.

Assured answered 24/10, 2008 at 13:52 Comment(4)
compiler? what does the compiler have to do with GC? :)Macadam
Nothing, it only compiles and optimizes code. And this definitely has nothing to do with the CLR... or .NET even.Assured
Objects occupying lots of memory, like very big images, can end up not getting garbage collected, unless you explicitly garbage collect them. I think this (large objects) was the OP's issue, more or less.Ferule
Wrapping it in a using will ensure that it's scheduled for GC once it's out of using scope. Unless the computer explodes, that memory will most likely be cleaned up.Assured
E
10

One case I recently encountered that required manual calls to GC.Collect() was when working with large C++ objects that were wrapped in tiny managed C++ objects, which in turn were accessed from C#.

The garbage collector never got called because the amount of managed memory used was negligible, but the amount of unmanaged memory used was huge. Manually calling Dispose() on the objects would require that I keep track of when objects are no longer needed myself, whereas calling GC.Collect() will clean up any objects that are no longer referred.....

Exteroceptor answered 21/9, 2012 at 8:50 Comment(3)
A better way to solve this is by calling GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource) in the constructor and later GC.RemoveMemoryPressure(addedSize) in the finalizer. This way the garbage collector will run automatically, taking into account the size of the unmanaged structures that could be collected. #1149681Muscadine
And an even better way to solve the problem is to actually call Dispose(), which you are supposed to do anyway.Eckard
Best way is to use the Using structure. Try/Finally .Dispose is a hassleBedell
H
9

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.

Handtomouth answered 24/10, 2008 at 13:53 Comment(4)
Are you sure that you need this? The GC will collect if it needs memory, even if your code is not idle.Deportation
Not sure how it is in .net 3.5 SP1 now, but previously (1.1 and i believe i tested against 2.0) it did make a difference in memory usage. The GC will of course always collect when needed, but you might still end up wasting 100 Megs of RAM when you only need 20. Would need a few´more tests thoughHandtomouth
GC is triggered on memory allocation when generation 0 reaches a certain threshold (for example 1MB), not when "something is idle". Otherwise you could end up with OutOfMemoryException in a loop by simply allocating and immediately discarding objects.Lanford
the 100megs of RAM isn't wasted if no other processes needed it. It's giving you a nice performance boost :-PDisconsolate
T
7

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.
Totalitarian answered 24/10, 2008 at 13:54 Comment(0)
T
5

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.

Trouper answered 2/11, 2008 at 2:19 Comment(0)
D
5

I would like to add that: Calling GC.Collect() (+ WaitForPendingFinalizers()) is one part of the story. As rightly mentioned by others, GC.COllect() is non-deterministic collection and is left to the discretion of the GC itself (CLR). Even if you add a call to WaitForPendingFinalizers, it may not be deterministic. Take the code from this msdn link and run the code with the object loop iteration as 1 or 2. You will find what non-deterministic means (set a break point in the object's destructor). Precisely, the destructor is not called when there were just 1 (or 2) lingering objects by Wait..().[Citation reqd.]

If your code is dealing with unmanaged resources (ex: external file handles), you must implement destructors (or finalizers).

Here is an interesting example:

Note: If you have already tried the above example from MSDN, the following code is going to clear the air.

class Program
{    
    static void Main(string[] args)
        {
            SomePublisher publisher = new SomePublisher();

            for (int i = 0; i < 10; i++)
            {
                SomeSubscriber subscriber = new SomeSubscriber(publisher);
                subscriber = null;
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(SomeSubscriber.Count.ToString());


            Console.ReadLine();
        }
    }

    public class SomePublisher
    {
        public event EventHandler SomeEvent;
    }

    public class SomeSubscriber
    {
        public static int Count;

        public SomeSubscriber(SomePublisher publisher)
        {
            publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
        }

        ~SomeSubscriber()
        {
            SomeSubscriber.Count++;
        }

        private void publisher_SomeEvent(object sender, EventArgs e)
        {
            // TODO: something
            string stub = "";
        }
    }

I suggest, first analyze what the output could be and then run and then read the reason below:

{The destructor is only implicitly called once the program ends. } In order to deterministically clean the object, one must implement IDisposable and make an explicit call to Dispose(). That's the essence! :)

Dulia answered 17/3, 2014 at 17:3 Comment(0)
L
4

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.

Lanford answered 24/10, 2008 at 14:24 Comment(0)
T
2

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.

Trouper answered 2/11, 2008 at 2:24 Comment(1)
I think it's better that you merge this answer with your previous answer.Pia
C
1

Not sure if it is a best practice...

Suggestion: do not implement this or anything when unsure. Reevaluate when facts are known, then perform before/after performance tests to verify.

Calisaya answered 10/1, 2012 at 0:12 Comment(0)
D
0

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...

Disconsolate answered 2/11, 2008 at 2:39 Comment(1)
"then either you are mistaken, or your program has been built wrong. Either way, the solution is not forcing the GC..." Absolutes are almost always not true. There are some exceptional circumstances it makes sense.Heptad
V
0

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.

Val answered 1/7, 2022 at 15:28 Comment(0)
P
-2

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()
Philibeg answered 19/8, 2021 at 20:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.