When is it acceptable to call GC.Collect?
Asked Answered
T

23

208

The general advice is that you should not call GC.Collect from your code, but what are the exceptions to this rule?

I can only think of a few very specific cases where it may make sense to force a garbage collection.

One example that springs to mind is a service, that wakes up at intervals, performs some task, and then sleeps for a long time. In this case, it may be a good idea to force a collect to prevent the soon-to-be-idle process from holding on to more memory than needed.

Are there any other cases where it is acceptable to call GC.Collect?

Tiling answered 25/1, 2009 at 20:4 Comment(2)
Scott Holden's blog entry on when to (and when not to) call GC.Collect is specific to the .NET Compact Framework, but the rules generally apply to all managed development.Settera
Possible duplicate of Best Practice for Forcing Garbage Collection in C#Felecia
C
176

If you have good reason to believe that a significant set of objects - particularly those you suspect to be in generations 1 and 2 - are now eligible for garbage collection, and that now would be an appropriate time to collect in terms of the small performance hit.

A good example of this is if you've just closed a large form. You know that all the UI controls can now be garbage collected, and a very short pause as the form is closed probably won't be noticeable to the user.

UPDATE 2.7.2018

As of .NET 4.5 - there is GCLatencyMode.LowLatency and GCLatencyMode.SustainedLowLatency. When entering and leaving either of these modes, it is recommended that you force a full GC with GC.Collect(2, GCCollectionMode.Forced).

As of .NET 4.6 - there is the GC.TryStartNoGCRegion method (used to set the read-only value GCLatencyMode.NoGCRegion). This can itself, perform a full blocking garbage collection in an attempt to free enough memory, but given we are disallowing GC for a period, I would argue it is also a good idea to perform full GC before and after.

Source: Microsoft engineer Ben Watson's: Writing High-Performance .NET Code, 2nd Ed. 2018.

See:

Chavira answered 25/1, 2009 at 20:9 Comment(12)
According to MS source code calling GC.Collect(2) every 850ms is just fine. Don't believe? Then just see PresentationCore.dll, MS.Internal.MemoryPressure.ProcessAdd(). I currently have an image processing app (small images, nothing with real memory pressure) where calling GC.Collect(2) takes longer than 850ms and so the entire app gets frozen by this (app spending 99.7% of time in GC).Appeal
@springy76: Being done by Microsoft in one place doesn't mean it's deemed a good thing by those giving advice from Microsoft...Chavira
They just proved with this that calling GC.Collect() doesn't do anything good.Appeal
I don't like that example. What's the poing in doing it after form close? One good example I see is for after loading game level on XBox or WindowsPhone. On those platforms GC runs after allocating 1MB or sth like that. So it's good to allocate as much as you can during loading of the level (while showing some splash screen) and then do GC.Collect to try to avoid collections during the game.Tailored
@Peri: The point of doing it after form close is that you've just made a bunch of objects (controls, the data you were displaying) eligible for garbage collection - so by calling GC.Collect you're basically telling the garbage collector that you know better than it for a change. Why don't you like the example?Chavira
@JonSkeet: I didn't downvote :-) But I cannot see any valuable reason for calling GC.Colllect in your example. GC.Collect will perform when ever it's Gen 0 full or will collect memory in large object heap by using its heuristic algorithms. What I am not understand is the gain you receive by calling GC.Collect programmatically?Paley
@SHCJ: GC.Collect() will request that the GC performs a full collection. If you know that you've just made a lot of previously-long-lived objects eligible for garbage collection and you believe the user is less likely to notice a slight pause now than later, it seems entirely reasonable to think that now is a better time to prompt a collection than letting it happen later.Chavira
@JonSkeet: +1. That makes sense; suspending all threads by calling GC.Collect when you think user is happy to wait a bit.Paley
I just had a case, where I "needed" to call GC.Collect(): I did a batch processing of images, where I load an image, resize it to two different sized images and then save them to disk: Even with using using-statements around all Image-instances the GC took several seconds to clear the old objects --> >500MB RAM "unneccessarly" used, as the actual processing is filling it that fast --> after calling GC.Collect() after each processed image did not slow down the process noticable, but the RAM usage is stable at a few MBs...Seeley
@Jon Skeet - uh oh! Your point is a fair one, but I fear is is the route to someone implementing GC.collect as a standard after every form is closed. It could become an 'expert tip' or the WITH(NOLOCK) of c# lolAmbiguous
@JonSkeet how about a continuous uploader? Should I call GC.Collect every time I have uploaded a file? the files varies its sizes, records could be from 2 to over a millionAsaasabi
@Mr.J: I wouldn't do anything unless you see there's a reason, and then you should test.Chavira
M
55

I use GC.Collect only when writing crude performance/profiler test rigs; i.e. I have two (or more) blocks of code to test - something like:

GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestA(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestB(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
...

So that TestA() and TestB() run with as similar state as possible - i.e. TestB() doesn't get hammered just because TestA left it very close to the tipping point.

A classic example would be a simple console exe (a Main method sort-enough to be posted here for example), that shows the difference between looped string concatenation and StringBuilder.

If I need something precise, then this would be two completely independent tests - but often this is enough if we just want to minimize (or normalize) the GC during the tests to get a rough feel for the behaviour.

During production code? I have yet to use it ;-p

Millimicron answered 25/1, 2009 at 20:31 Comment(1)
And I probably add "WaitForPendingFinalizers" (or whatever it is) too in this case ;-pMillimicron
B
34

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 speeded 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 match 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"


These days I consider same of the above cases would be better to use a short lived worker process to do each batch of work and let the OS do the resource recovery.

Burnham answered 24/9, 2009 at 15:47 Comment(0)
A
24

One case is when you are trying to unit test code that uses WeakReference.

Admit answered 25/1, 2009 at 20:8 Comment(0)
A
17

In large 24/7 or 24/6 systems -- systems that react to messages, RPC requests or that poll a database or process continuously -- it is useful to have a way to identify memory leaks. For this, I tend to add a mechanism to the application to temporarily suspend any processing and then perform full garbage collection. This puts the system into a quiescent state where the memory remaining is either legitimately long lived memory (caches, configuration, &c.) or else is 'leaked' (objects that are not expected or desired to be rooted but actually are).

Having this mechanism makes it a lot easier to profile memory usage as the reports will not be clouded with noise from active processing.

To be sure you get all of the garbage, you need to perform two collections:

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

As the first collection will cause any objects with finalizers to be finalized (but not actually garbage collect these objects). The second GC will garbage collect these finalized objects.

Arva answered 21/3, 2011 at 21:36 Comment(2)
I've seen the two-pass collection in a couple of places now, but after reading the passage in the MSDN documentation for GC.WaitForPendingFinalizers that says:" 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." I'm just a touch paranoid. Do you know of a definitive source for doing two passes?Rosariarosario
@jerhewet: The key to understanding why two collections are required is with understanding what happens to objects with finalizers. Unfortunately I don't have exactly what you're asking for, but have a read of this article and this question on SO.Arva
M
12

You can call GC.Collect() when you know something about the nature of the app the garbage collector doesn't.

As the author, it's often tempting to think this is likely or normal. However, the truth is the GC amounts to a pretty well-written and tested expert system, and it's rare you'll know something about the low level code paths it doesn't.

The best example I can think of where you might have some extra information is an app that cycles between idle periods and very busy periods. You want the best performance possible for the busy periods and therefore want to use the idle time to do some clean up.

However, most of the time the GC is smart enough to do this anyway.

Multinuclear answered 25/1, 2009 at 20:54 Comment(0)
S
10

One instance where it is almost necessary to call GC.Collect() is when automating Microsoft Office through Interop. COM objects for Office don't like to automatically release and can result in the instances of the Office product taking up very large amounts of memory. I'm not sure if this is an issue or by design. There's lots of posts about this topic around the internet so I won't go into too much detail.

When programming using Interop, every single COM object should be manually released, usually though the use of Marshal.ReleseComObject(). In addition, calling Garbage Collection manually can help "clean up" a bit. Calling the following code when you're done with Interop objects seems to help quite a bit:

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

In my personal experience, using a combination of ReleaseComObject and manually calling garbage collection greatly reduces the memory usage of Office products, specifically Excel.

Sexism answered 11/7, 2017 at 17:10 Comment(1)
yeah i ran into that with .net accessing excel which works via com objects too. It is important to note, that this wil not work well in DEBUG mode because GC operations are limited there. It will work as intend only in RELEASE mode. relevant link: #17130882Vermont
A
8

As a memory fragmentation solution. I was getting out of memory exceptions while writing a lot of data into a memory stream (reading from a network stream). The data was written in 8K chunks. After reaching 128M there was exception even though there was a lot of memory available (but it was fragmented). Calling GC.Collect() solved the issue. I was able to handle over 1G after the fix.

Anarthrous answered 17/10, 2013 at 23:6 Comment(1)
I believe that case has been solved with updates to the .Net GC system.Burnham
S
7

Have a look at this article by Rico Mariani. He gives two rules when to call GC.Collect (rule 1 is: "Don't"):

When to call GC.Collect()

Subvene answered 25/1, 2009 at 20:8 Comment(2)
Been there already. I'm not trying to find excuses for doing something that you shouldn't do, but I would like to know if there are any specific cases where it would be acceptable.Tiling
new linkAppalachian
W
6

I was doing some performance testing on array and list:

private static int count = 100000000;
private static List<int> GetSomeNumbers_List_int()
{
    var lstNumbers = new List<int>();
    for(var i = 1; i <= count; i++)
    {
        lstNumbers.Add(i);
    }
    return lstNumbers;
}
private static int[] GetSomeNumbers_Array()
{
    var lstNumbers = new int[count];
    for (var i = 1; i <= count; i++)
    {
        lstNumbers[i-1] = i + 1;
    }
    return lstNumbers;
}
private static int[] GetSomeNumbers_Enumerable_Range()
{
    return  Enumerable.Range(1, count).ToArray();
}

static void performance_100_Million()
{
    var sw = new Stopwatch();

    sw.Start();
    var numbers1 = GetSomeNumbers_List_int();
    sw.Stop();
    //numbers1 = null;
    //GC.Collect();
    Console.WriteLine(String.Format("\"List<int>\" took {0} milliseconds", sw.ElapsedMilliseconds));

    sw.Reset();
    sw.Start();
    var numbers2 = GetSomeNumbers_Array();
    sw.Stop();
    //numbers2 = null;
    //GC.Collect();
    Console.WriteLine(String.Format("\"int[]\" took {0} milliseconds", sw.ElapsedMilliseconds));

    sw.Reset();
    sw.Start();
//getting System.OutOfMemoryException in GetSomeNumbers_Enumerable_Range method
    var numbers3 = GetSomeNumbers_Enumerable_Range();
    sw.Stop();
    //numbers3 = null;
    //GC.Collect();

    Console.WriteLine(String.Format("\"int[]\" Enumerable.Range took {0} milliseconds", sw.ElapsedMilliseconds));
}

and I got OutOfMemoryException in GetSomeNumbers_Enumerable_Range method the only workaround is to deallocate the memory by:

numbers = null;
GC.Collect();
Whitneywhitson answered 8/9, 2017 at 18:30 Comment(1)
Why down voting? my answer is an example which demonstrate when to call GC. Do you have a better suggestion?You are welcome to present.Whitneywhitson
L
5

You should try to avoid using GC.Collect() since its very expensive. Here is an example:

        public void ClearFrame(ulong timeStamp)
    {
        if (RecordSet.Count <= 0) return;
        if (Limit == false)
        {
            var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
            if (seconds <= _preFramesTime) return;
            Limit = true;
            do
            {
                RecordSet.Remove(RecordSet[0]);
            } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
        }
        else
        {
            RecordSet.Remove(RecordSet[0]);

        }
        GC.Collect(); // AVOID
    }

TEST RESULT: CPU USAGE 12%

When you change to this:

        public void ClearFrame(ulong timeStamp)
    {
        if (RecordSet.Count <= 0) return;
        if (Limit == false)
        {
            var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
            if (seconds <= _preFramesTime) return;
            Limit = true;
            do
            {
                RecordSet[0].Dispose(); //  Bitmap destroyed!
                RecordSet.Remove(RecordSet[0]);
            } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
        }
        else
        {
            RecordSet[0].Dispose(); //  Bitmap destroyed!
            RecordSet.Remove(RecordSet[0]);

        }
        //GC.Collect();
    }

TEST RESULT: CPU USAGE 2-3%

Lablab answered 22/2, 2014 at 22:58 Comment(1)
Additionally, the first example leads to potential leaks anyway, and makes more work for the GC as well, because of having to process the finalize queue. The second example not only is better by not explicitly invoking the GC, but because the objects that are properly disposed are most likely not even going to live to gen 1, resulting in the second one being both faster and more memory-efficient.Oppilate
K
4

In your example, I think that calling GC.Collect isn't the issue, but rather there is a design issue.

If you are going to wake up at intervals, (set times) then your program should be crafted for a single execution (perform the task once) and then terminate. Then, you set the program up as a scheduled task to run at the scheduled intervals.

This way, you don't have to concern yourself with calling GC.Collect, (which you should rarely if ever, have to do).

That being said, Rico Mariani has a great blog post on this subject, which can be found here:

http://blogs.msdn.com/ricom/archive/2004/11/29/271829.aspx

Kamasutra answered 25/1, 2009 at 20:8 Comment(0)
L
4

One useful place to call GC.Collect() is in a unit test when you want to verify that you are not creating a memory leak (e. g. if you are doing something with WeakReferences or ConditionalWeakTable, dynamically generated code, etc).

For example, I have a few tests like:

WeakReference w = CodeThatShouldNotMemoryLeak();
Assert.IsTrue(w.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(w.IsAlive);

It could be argued that using WeakReferences is a problem in and of itself, but it seems that if you are creating a system that relies on such behavior then calling GC.Collect() is a good way to verify such code.

Leonoraleonore answered 3/7, 2013 at 20:33 Comment(0)
C
3

There are some situations where it is better safe than sorry.

Here is one situation.

It is possible to author an unmanaged DLL in C# using IL rewrites (because there are situations where this is necessary).

Now suppose, for example, the DLL creates an array of bytes at the class level - because many of the exported functions need access to such. What happens when the DLL is unloaded? Is the garbage collector automatically called at that point? I don't know, but being an unmanaged DLL it is entirely possible the GC isn't called. And it would be a big problem if it wasn't called. When the DLL is unloaded so too would be the garbage collector - so who is going to be responsible for collecting any possible garbage and how would they do it? Better to employ C#'s garbage collector. Have a cleanup function (available to the DLL client) where the class level variables are set to null and the garbage collector called.

Better safe than sorry.

Centenarian answered 18/8, 2011 at 20:14 Comment(1)
This guy has a point which is why all these DON'T arguments are mostly irrelevant. When you use an unmanaged library all you have are managed pointers to unmanaged objects. As the GC sees these pointers as infinitesimally small it will be in no rush to collect them. This is why it is critical to Dispose on unmanaged objects when they implement IDisposable.Wonacott
J
3

If you are creating a lot of new System.Drawing.Bitmap objects, the Garbage Collector doesn't clear them. Eventually GDI+ will think you are running out of memory and will throw a "The parameter is not valid" exception. Calling GC.Collect() every so often (not too often!) seems to resolve this issue.

Jeffry answered 27/5, 2020 at 2:17 Comment(1)
Calling Dispose() on all GDI+ object when finished with them is basic Winforms programming.Burnham
N
2

The short answer is: never!

Nitrometer answered 25/1, 2009 at 20:19 Comment(1)
Not even in this situation?Hutcherson
L
2
using(var stream = new MemoryStream())
{
   bitmap.Save(stream, ImageFormat.Png);
   techObject.Last().Image = Image.FromStream(stream);
   bitmap.Dispose();

   // Without this code, I had an OutOfMemory exception.
   GC.Collect();
   GC.WaitForPendingFinalizers();
   //
}
Lavinia answered 5/8, 2011 at 5:10 Comment(0)
B
2

Another reason is when you have a SerialPort opened on a USB COM port, and then the USB device is unplugged. Because the SerialPort was opened, the resource holds a reference to the previously connected port in the system's registry. The system's registry will then contain stale data, so the list of available ports will be wrong. Therefore the port must be closed.

Calling SerialPort.Close() on the port calls Dispose() on the object, but it remains in memory until garbage collection actually runs, causing the registry to remain stale until the garbage collector decides to release the resource.

From https://mcmap.net/q/30333/-usb-serial-port-unplugged-but-still-in-the-list-of-ports:

try
{
    if (port != null)
        port.Close(); //this will throw an exception if the port was unplugged
}
catch (Exception ex) //of type 'System.IO.IOException'
{
    System.GC.Collect();
    System.GC.WaitForPendingFinalizers();
}

port = null;
Blackberry answered 12/11, 2019 at 1:42 Comment(0)
C
1

i am still pretty unsure about this. I am working since 7 years on an Application Server. Our bigger installations take use of 24 GB Ram. Its hightly Multithreaded, and ALL calls for GC.Collect() ran into really terrible performance issues.

Many third party Components used GC.Collect() when they thought it was clever to do this right now. So a simple bunch of Excel-Reports blocked the App Server for all threads several times a minute.

We had to refactor all the 3rd Party Components in order to remove the GC.Collect() calls, and all worked fine after doing this.

But i am running Servers on Win32 as well, and here i started to take heavy use of GC.Collect() after getting a OutOfMemoryException.

But i am also pretty unsure about this, because i often noticed, when i get a OOM on 32 Bit, and i retry to run the same Operation again, without calling GC.Collect(), it just worked fine.

One thing i wonder is the OOM Exception itself... If i would have written the .Net Framework, and i can't alloc a memory block, i would use GC.Collect(), defrag memory (??), try again, and if i still cant find a free memory block, then i would throw the OOM-Exception.

Or at least make this behavior as configurable option, due the drawbacks of the performance issue with GC.Collect.

Now i have lots of code like this in my app to "solve" the problem:

public static TResult ExecuteOOMAware<T1, T2, TResult>(Func<T1,T2 ,TResult> func, T1 a1, T2 a2)
{

    int oomCounter = 0;
    int maxOOMRetries = 10;
    do
    {
        try
        {
            return func(a1, a2);
        }
        catch (OutOfMemoryException)
        {
            oomCounter++;
            if (maxOOMRetries > 10)
            {
                throw;
            }
            else
            {
                Log.Info("OutOfMemory-Exception caught, Trying to fix. Counter: " + oomCounter.ToString());
                System.Threading.Thread.Sleep(TimeSpan.FromSeconds(oomCounter * 10));
                GC.Collect();
            }
        }
    } while (oomCounter < maxOOMRetries);

    // never gets hitted.
    return default(TResult);
}

(Note that the Thread.Sleep() behavior is a really App apecific behavior, because we are running a ORM Caching Service, and the service takes some time to release all the cached objects, if RAM exceeds some predefined values. so it waits a few seconds the first time, and has increased waiting time each occurence of OOM.)

Cubic answered 26/6, 2013 at 7:30 Comment(2)
A Component shouldn't call GC.Collect. Since it has an application wide effect only the application should do so (if at all).Geologize
If i would have written the .Net Framework, and i can't alloc a memory block, i would use GC.Collect(), - I think they're already doing this - I've seen indications that one of the internal GC triggers is certain failures to allocate memory.Bless
B
1

one good reason for calling GC is on small ARM computers with little memory, like the Raspberry PI (running with mono). If unallocated memory fragments use too much of the system RAM, then the Linux OS can get unstable. I have an application where I have to call GC every second (!) to get rid of memory overflow problems.

Another good solution is to dispose objects when they are no longer needed. Unfortunately this is not so easy in many cases.

Burnley answered 8/1, 2017 at 14:13 Comment(0)
P
0

This isn't that relevant to the question, but for XSLT transforms in .NET (XSLCompiledTranform) then you might have no choice. Another candidate is the MSHTML control.

Pontifical answered 25/1, 2009 at 20:36 Comment(0)
K
0

If you are using a version of .net less than 4.5, manual collection may be inevitable (especially if you are dealing with many 'large objects').

this link describes why:

https://blogs.msdn.microsoft.com/dotnet/2011/10/03/large-object-heap-improvements-in-net-4-5/

Kannada answered 16/8, 2016 at 10:46 Comment(0)
I
0

Since there are Small object heap(SOH) and Large object heap(LOH)

We can call GC.Collect() to clear de-reference object in SOP, and move lived object to next generation.

In .net4.5, we can also compact LOH by using largeobjectheapcompactionmode

Insomnolence answered 28/12, 2017 at 2:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.