135

So I was reading a question about forcing the C# garbage collector to run where almost every single answer is the same: you can do it, but you shouldn't - except for some very rare cases. Sadly, nobody there elaborates on what are such cases.

Can you tell me in what sort of scenario it is actually a good or reasonable idea to force garbage collection?

I'm not asking for C# specific cases but rather, all programing languages that have a garbage collector. I know that you can't force GC on all languages, like Java, but let's suppose you can.

  • 17
    "but rather, all programing languages that have a garbage collector" Different languages (or, more properly, different implementations) use different methods for garbage collection, so you're unlikely to find a one-size-fits-all rule. – Colonel Thirty Two Mar 17 '15 at 23:13
  • 4
    @Doval If you're under a real time constraint and the GC doesn't provide matching guarantees, you're between a rock and a hard place. It might reduce undesired pauses vs. doing nothing, but from what I've heard it's "easier" to avoid allocating in the normal course of operation. – user7043 Mar 17 '15 at 23:25
  • 3
    I was under the impression that if you were expecting to have hard real-time deadlines, you would never use a GC language in the first place. – GregRos Mar 17 '15 at 23:29
  • 4
    I can't see how you can answer this in a non-VM-specific way. Relevant for 32-bit processes, relevantless for 64-bit processes. .NET JVM and for the high-end one – rwong Mar 18 '15 at 0:26
  • 3
    @DavidConrad you can force it in C#. Hence the question. – Omega Mar 19 '15 at 23:23

10 Answers 10

127

You really can't make blanket statements about appropriate way to use all GC implementations. They vary wildly. So I'll speak to the .NET one which you originally referred to.

You must know the behaviour of the GC pretty intimately to do this with any logic or reason.

The only advice on collection I can give is: Never do it.

If you truly know the intricate details of the GC, you'll not need my advice so it won't matter. If you don't already know with 100% confidence it will help, and have to look online and find an answer like this: You should not be calling GC.Collect, or alternatively: You should go learn the details of how the GC works inside and out, and only then will you know the answer.

There is one safe place it makes some sense to use GC.Collect:

GC.Collect is an API available that you can use for profiling timings of things. You could profile one algorithm, collect, and profile another algorithm immediately afterwards knowing GC of the first algo wasn't occurring during your second one skewing the results.

This sort of profiling is the single time I would ever suggest manually collecting to anyone.


Contrived Example Anyway

One possible use case is if you load really large things, they'll end up in the Large Object Heap which will go straight to Gen 2, though again Gen 2 is for long lived objects because it collects less frequently. If you know that you are loading short lived objects into Gen 2 for any reason, you could clear them out more quickly to keep your Gen 2 smaller and it's collections faster.

This is the best example I could come up with, and it's not good - the LOH pressure you're building here would cause more frequent collections, and collections are so frequent as it is - chances are it would be clearing out the LOH just as fast as you were blowing it out with temporary objects. I simply don't trust myself to presume a better collection frequency than the GC itself - tuned by people far far smarter than I.


So let's talk about some of the semantics and mechanisms in the .NET GC... or..

Everything I think I know about the .NET GC

Please, anyone who finds errors here - do correct me. Much of the GC is well known to be black magic and while I tried to leave out details I was uncertain of, I probably still got some things wrong.

Below is purposely missing numerous details I'm not certain about, as well as a far larger body of information I'm simply unaware of. Use this information at your own risk.


GC Concepts

The .NET GC occurs at inconsistent times, which is why it's called "non-deterministic", this means you can't rely on it to occur at specific times. It's also a generational garbage collector, which means it partitions your objects into how many GC passes they've lived through.

Objects in Gen 0 heap have lived through 0 collections, these have been newly made so recently no collection has occurred since their instantiation. Objects in your Gen 1 heap have lived through one collection pass, and likewise objects in your Gen 2 heap have lived through 2 collection passes.

Now it's worth noting the reason it qualifies these specific generations and partitions accordingly. The .NET GC only recognizes these three generations, because the collection passes that go over these three heaps are all slightly different. Some objects may survive collection passes thousands of times. The GC merely leaves these on the other side of the Gen 2 heap partition, there's no point in partitioning them anywhere further because they're actually Gen 44; the collection pass on them is the same as everything in Gen 2 heap.

There are semantic purposes to these specific generations, as well as implemented mechanisms that honor these, and I'll get to those in a moment.


What's in a collection

The basic concept of a GC collection pass is that it checks each object in a heap space to see if there are still live references (GC roots) to these objects. If a GC root is found for an object, it means currently executing code can still possible reach and use that object, so it cannot be deleted. However if a GC root is not found for an object, it means the running process no longer needs the object, so it can remove it to free up memory for new objects.

Now after it's finished cleaning up a bunch of objects and leaving some alone, there will be an unfortunate side effect: Free space gaps between live objects where the dead ones were removed. This memory fragmentation if left alone would simply waste memory, so collections will typically do what's called "compaction" where they take all the live objects left and squeeze them together in the heap so the free memory is contiguous on one side of the heap for Gen 0.

Now given the idea of 3 heaps of memory, all partitioned by the number of collection passes they've lived through, let's talk about why these partitions exist.


Gen 0 Collection

Gen 0 being the absolute newest objects, tends to be very small - so you can safely collect it very frequently. The frequency ensures the heap stays small and the collections are very fast because they are collecting over such a small heap. This is based more or less on a heuristic that claims: A large majority of temporary objects which you create, are very temporary, so temporary they'll no longer be used or referenced almost immediately after use, and thus can be collected.


Gen 1 Collection

Gen 1 being objects that didn't fall into this very temporary category of objects, may still be rather short lived, because still- a vast portion of the objects created are not used for long. Therefore Gen 1 collects rather frequently as well, again keeping it's heap small so it's collections are fast. However the assumption is less of it's objects are temporary than Gen 0, so it collects less frequently than Gen 0

I will say I frankly don't know the technical mechanisms that differ between Gen 0's collection pass and Gen 1's, if there are any at all other than the frequency they collect.


Gen 2 Collection

Gen 2 now must be the mother of all heaps right? Well, yes, that's more or less right. It's where all your permanent objects live - the object your Main() lives in for instance, and everything that Main() references because those will be rooted until your Main() returns at the end of your process.

Given that Gen 2 is a bucket for basically everything the other generations couldn't collect, it's objects are largely permanent, or long lived at the least. So recognizing very little of what's in Gen 2 will actually be something that can be collected, it doesn't have need to collect frequently. This allows it's collection to also be slower, since it executes so much less frequent. So this is basically where they've tacked on all the extra behaviours for odd scenarios, because they have the time to execute them.


Large Object Heap

One example of the extra behaviours of Gen 2 is that it also does the collection on the Large Object Heap. Up until now I've been talking entirely about the Small Object Heap, but the .NET runtime allocates things of certain sizes to a separate heap because of what I referred to as compaction above. Compaction requires moving objects around when collections finish on the Small Object Heap. If there's a living 10mb object in Gen 1, it's going to take far longer for it to complete the compaction after collection, thus slowing down Gen 1's collection. So that 10mb object is allocated to the Large Object Heap, and collected during Gen 2 which runs so infrequently.


Finalization

Another example is objects with finalizers. You put a finalizer on an object that references resources beyond the scope of .NETs GC (unmanaged resources). The finalizer is the only way the GC gets to demand an unmanaged resource is collected - you implement your finalizer to do the manual collection/removal/release of the unmanaged resource to ensure it doesn't leak from your process. When the GC gets to executing your objects finalizer, then your implementation will clear the unmanaged resource, making the GC capable of removing your object without risking a resource leak.

The mechanism with which finalizers do this is by being referenced directly in a finalization queue. When the runtime allocates an object with a finalizer, it adds a pointer to that object to the finalization queue, and locks your object in place (called pinning) so compaction won't move it which would break the finalization queue reference. As collection passes occur, eventually your object will be found to no longer have a GC root, but the finalization must be executed before it can be collected. So when the object is dead, the collection will move it's reference from the finalization queue and place a reference to it on what's known as the "FReachable" queue. Then the collection continues on. At another "non-deterministic" time in the future, a separate thread known as the Finalizer thread will go through the FReachable queue, executing the finalizers for each of the objects referenced. After it's finished, the FReachable queue is empty, and it has flipped a bit on the header of each object that says they don't need finalization (This bit can also be flipped manually with GC.SuppressFinalize which is common in Dispose() methods), I also suspect it has unpinned the objects, but don't quote me on that. The next collection that comes around on whatever heap this object is in, will finally collect it. Gen 0 collections don't even pay attention to objects with that finalization-needed bit on, it automatically promotes them, without even checking for their root. An unrooted object needing finalization in Gen 1, will get tossed on the FReachable queue, but the collection doesn't do anything else with it, so it lives into Gen 2. In this way, all objects which have a finalizer, and don't GC.SuppressFinalize will be collected in Gen 2.

  • 4
    @FlorianMargaine yeah... saying anything about "GC" across all implementations really doesn't make sense.. – Jimmy Hoffa Mar 18 '15 at 0:35
  • 10
    tl;dr: Use object pools instead. – Robert Harvey Mar 18 '15 at 4:31
  • 5
    tl;dr: For timing/profiling, it can be useful. – kutschkem Mar 18 '15 at 8:08
  • 3
    @Den after reading my description above of the mechanics (as I understand them), what would be the benefit as you see it? You clean out a large number of objects - in the SOH (or LOH?)? Did you just cause other threads to pause for this collection? Did that collection just promote twice as many objects to Gen 2 as it cleared out? Did the collection cause compaction on LOH (do you have it turned on?)? How many GC heaps do you have and is your GC in server or desktop mode? GC is a feckin' ice berg, the treachery is below the waters. Just steer clear. I'm not smart enough to comfortably collect. – Jimmy Hoffa Mar 18 '15 at 14:58
  • 4
    @RobertHarvey Object pools aren't a silver bullet either. The garbage collector's generation 0 is already effectively an object pool - it's usually sized to fit in the smallest cache level and thus new objects are generally created in memory that's already in cache. Your object pool is now competing against the GC's nursery for the cache, and if the sum of the GC's nursery and your pool is bigger than the cache you're obviously going to have cache misses. And if you plan on using parallelism now you have to re-implement synchronization and worry about false sharing. – Doval Mar 18 '15 at 19:50
68

Sadly, nobody there elaborates on what are such cases.

I'll give some examples. All in all it is rare that forcing a GC is a good idea but it can be totally worth it. This answer is from my experience with .NET and GC literature. It should generalize well to other platforms (at least those that have a significant GC).

  • Benchmarks of various kinds. You want a known managed heap state when a benchmark begins so that the GC does not randomly trigger during benchmarks. When you repeat a benchmark you want the same number and amount of GC work in each repetition.
  • Sudden release of resources. For example closing a significant GUI Window or refreshing a cache (and thereby releasing the old potentially big cache contents). The GC cannot detect this because all you are doing is setting a reference to null. The fact that this orphans an entire object graph is not easily detectable.
  • Release of unmanaged resources that have leaked. This should never happen, of course, but I have seen cases where a 3rd party library leaked stuff (such as COM objects). The developer was forced to sometimes induce a collection.
  • Interactive applications such as games. During play games have very strict time budgets per frame (60Hz => 16ms per frame). In order to avoid hickups you need a strategy to deal with GCs. One such strategy is to delay G2 GCs as much as possible and force them at an opportune time such as a loading screen or a cut scene. The GC cannot know when the best such moment is.
  • Latency control in general. Some web applications disable GCs and periodically run a G2 collection while being switched out of load balancer rotation. That way the G2 latency is never surfaced to the user.

If your goal is throughput then the rarer the GC the better. In those cases forcing a collection cannot have a positive impact (except for rather contrived issues such as increasing CPU cache utilization by removing dead objects interspersed in the live ones). Batch collection is more efficient for all collectors I know of. For production app in steady-state memory consumption inducing a GC does not help.

The examples given above target consistency and boundedness of memory usage. In those cases induced GCs can make sense.

There seems to be a wide-spread idea that the GC is a divine entity that induces a collection whenever it is indeed optimal to do so. No GC I know of is that sophisticated and indeed it is very hard to be optimal for the GC. The GC knows less than the developer does. It's heuristics are based on memory counters and things like collection rate and so on. The heuristics are usually good but they do not capture sudden changes in application behavior such as release of big amounts of managed memory. It also is blind to unmanaged resources and to latency requirements.

Note, that GC costs vary with heap size and number of references on the heap. On a small heap the cost can be very small. I have seen G2 collection rates with .NET 4.5 of 1-2GB/sec on a production app with 1GB heap size.

  • For the latency control case, I guess instead of doing this periodically, you could also do it by need (i.e. when the memory usage grows over a certain threshold). – Paŭlo Ebermann Mar 19 '15 at 19:34
  • 3
    +1 for the second to last paragraph. Some people have the same sentiment about compilers and are quick to call almost anything "premature optimization". I usually tell them something similar. – Honza Brabec Mar 20 '15 at 9:19
  • 2
    +1 for that paragraph as well. I find it shocking that people think a computer program written by someone else must necessarily understand the performance characteristics of their program better than themselves. – Mehrdad Mar 21 '15 at 19:05
  • 1
    @HonzaBrabec The problem is the same in both cases: If you think you know better than the GC or the compiler, then it's very easy to hurt yourself. If you actually know more, then you're optimizing only when you know it's not premature. – svick May 3 '15 at 21:13
27

As a general principle, a garbage collector will collect when it runs into "memory pressure", and it's considered a good idea to not have it collect at other times because you could cause performance problems or even noticeable pauses in your program's execution. And in fact, the first point is dependent on the second: for a generational garbage collector, at least, it runs more efficiently the higher the ratio of garbage to good objects becomes, so in order to minimize the amount of time spent pausing the program, it has to procrastinate and let the garbage pile up as much as possible.

The appropriate time to manually invoke the garbage collector, then, is when you've finished doing something that 1) is likely to have created a lot of garbage, and 2) is expected by the user to take some time and leave the system unresponsive anyway. A classic example is at the end of loading something large (a document, a model, a new level, etc.)

12

One thing no one has mentioned is that, while the Windows GC is amazingly good, the GC on Xbox is garbage (pun intended).

So when coding an XNA game intended to run on XBox, it's absolutely crucial to time garbage collection to opportune moments, or you'll have horrible intermittent FPS hiccups. Additionally, on XBox it's common to use structs way, way more often than you normally would, to minimize the number of objects that need to be garbage-collected.

4

Garbage collection is first and foremost a memory management tool. As such, garbage collectors will collect when there is memory pressure.

Modern garbage collectors are very good, and getting better, so it's unlikely that you can improve on them by collecting manually. Even if you can improve things today, it may well be that a future improvement to your chosen garbage collector will make your optimisation ineffective, or even counterproductive.

However, garbage collectors typically do not attempt to optimise use of resources other than memory. In garbage collected environments, most valuable non-memory resources have a close method or similar, but there are some occasions where this isn't the case for some reason, such as compatibility with an existing API.

In these cases it may make sense to manually invoke garbage collection when you know that a valuable non-memory resource is being used.

RMI

One concrete example of this is Java's Remote Method Invocation. RMI is a remote procedure call library. You typically have a server, which makes various objects available for use by clients. If a server knows that an object isn't being used by any clients, then that object is eligible for garbage collection.

However, the only way that the server knows this is if the client tells it, and the client only tells the server that it doesn't need an object any more once the client has garbage collected whatever is using it.

This presents a problem, since the client may have lots of free memory, so may not run garbage collection very frequently. Meanwhile, the server may have lots of unused objects in memory, that it can't collect because it doesn't know that the client isn't using them.

The solution in RMI is for the client to run garbage collection periodically, even when it has lots of memory free, to ensure that objects are collected promptly on the server.

  • "In these cases it may make sense to manually invoke garbage collection when you know that a valuable non-memory resource is being used" -- if a non-memory resource is being used you should be using a using block or otherwise calling a Close method to ensure the resource is discarded as soon as possible. Relying on GC to clean up non-memory resources is unreliable, and causes all kinds of problems (particularly with files that need to be locked for access so can only be open once). – Jules Jan 29 '18 at 18:34
  • And as stated in the answer, when a close method is available (or the resource can be used with a using block), these are the right approach. The answer specifically deals with the rare cases where these mechanisms aren't available. – James_pic Jan 30 '18 at 18:42
  • My personal opinion is that any interface that manages a non-memory resource but doesn't provide a close method is an interface that should not be used, because there's no way to use it reliably. – Jules Jan 31 '18 at 9:56
  • @Jules I agree, but sometimes it's unavoidable. Sometimes abstractions leak, and using a leaky abstraction is better than using no abstraction. Sometimes you need to work with legacy code that demands you make promises you know you can't keep. Yes, it's rare, and should be avoided if possible, and there's a reason that there are all these warnings around forcing garbage collection, but these situations do come up, and the OP was asking what these situations look like - which I've answered. – James_pic Jan 31 '18 at 10:44
2

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"

2

There are several cases where you might want to call gc() yourself.

  • [Some people say that this is not good because it may promote objects to older generation space which I agree is not a good thing. However, it is NOT always true that there will always be objects which can be promoted. It is certainly possible that after this gc() call, very few objects remain let alone be moved into older generation space] When you are going to create a big collection of objects and use lot of memory. You simply want to clear out as much space as preparation as possible. This is just common sense. By calling gc() manually, there will not be redundant reference graph check on part of that big collection of objects that you are loading into memory. In short, if you run gc() before you load a lot into memory, the gc() induced during the load happens less by at least one time when loading start creating memory pressure.
  • When you have done loading a big collection of big objects and you will unlikely load more objects into memory. In short, you move from creating phase into using phase. By calling gc() depending on implementation, the memory in used will be compacted which massively improves cache locality. This will result in massive improve in performance that you will not get from profiling.
  • Similar to the first one, but from the view that if you do gc() and the memory management implementation supports, you will create much better continuity for your physical memory. This again makes new big collection of objects more continuous and compact which in turn improves performance
  • 1
    Can someone point out the reason of the downvote? I myself don't know enough to judge the answer (at first glance it kinda makes sense to me). – Omega Mar 18 '15 at 4:12
  • 1
    I'm guessing you got a downvote for the third point. Potentially also for saying "This is just common sense". – immibis Mar 18 '15 at 4:40
  • 2
    When you create a big collection of object the GC should be smart enough to know wether a collection is needed. Same when the memory need to be compacted. Relying on the GC to optimize memory locality of related objects doesn't seems realiable. I think you can find other solutions (struct, unsafe, ...). (I'm not the downvoter). – Guillaume Mar 18 '15 at 10:27
  • 3
    Your first idea of an ok time is just bad advice in my opinion. Chances are high that there has been a collection recently so your attempt at collecting again is simply going to arbitrarily promote objects to later generations, which is almost always bad. Later generations have collections that take longer to begin with, increasing their heap sizes "to clear out as much space as possible" just causes this to be more problematic. Plus if you're about to increase memory pressure with a load, you're likely to begin inducing collections anyway, which will run more slowly because increased Gen1/2 – Jimmy Hoffa Mar 18 '15 at 14:48
  • 2
    By calling gc() depending on implementation, the memory in used will be compacted which massively improves cache locality. This will result in massive improve in performance that you will not get from profiling. If you allocate a ton of objects in a row odds are they're already compacted. If anything, garbage collection may shuffle them around slightly. Either way, using data structures that are dense and not jumping around randomly in memory is going to have a bigger impact. If you're using a naive one-element-per-node linked list, no amount of manual GC trickery will make up for that. – Doval Mar 19 '15 at 1:23
2

A real-world example:

I had a web application that made use of a very large set of data that rarely changed and which needed to be accessed very quickly (quick enough for per-keystroke response via AJAX).

The obvious enough thing to do here is to load the relevant graph into memory, and access it from there rather than the database, updating the graph when the DB changes.

But being very large, a naïve load would have taken up at least 6GB of memory with the data due to grow in the future. (I don't have exact figures, once it was clear that my 2GB machine was trying to cope with at least 6GB I had all the measurements I needed to know it wasn't going to work).

Luckily, there was a large number of popsicle-immutable objects in this set of data that were the same as each other; once I'd worked out that a certain batch was the same as another batch, I could alias one reference to the other allowing lots of the data to be collected and therefore fit everything into less than half a gig.

All well and good, but for this still churning through over 6GB of objects in the space of about half a minute to get to this state. Left to its own, GC didn't cope; the spike in activity over the usual pattern of the application (much less heavy on deallocations per second) was too sharp.

So periodically calling GC.Collect() during this build process meant that the whole thing worked smoothly. Of course, I did not manually call GC.Collect() the rest of the time the application runs.

This real-world case is a good example of the guidelines of when we should use GC.Collect():

  1. Use with a relatively rare case of lots of objects being made available for collection (megabytes worth was being made available, and this graph-building was a very rare case over the lifetime of the application (about one minute per week).
  2. Do it when a loss in performance is relatively tolerable; this happened only on application start-up. (Another good example of this rule is between levels during a game, or other points in a game where players won't be upset by a bit of a pause).
  3. Profile to be sure there really is an improvement. (Pretty easy; "It works" almost always beats "it doesn't work").

Most of the time when I've thought I might have a case where GC.Collect() is worth calling, because point 1 and 2 applied, point 3 suggested it made things worse or at least made things no better (and with little or no improvement I'd lean toward not calling over calling as the approach more likely to prove better over the lifetime of an application).

0

I have a usage for garbage disposal which is somewhat unorthodox.

There is this misguided practice which is unfortunately very prevalent in the C# world, of implementing object disposal using the ugly, clunky, inelegant, and error prone idiom known as IDisposable-disposing. MSDN describes it in length, and lots of people swear by it, follow it religiously, spend hours upon hours discussing precisely how it should be done, etc.

(Please note that what I am calling ugly here is not the object disposal pattern itself; what I am calling ugly is the particular IDisposable.Dispose( bool disposing ) idiom.)

This idiom was invented because it is supposedly impossible to guarantee that your objects' destructor will always be invoked by the garbage collector to clean up resources, so people perform resource cleanup within IDisposable.Dispose(), and in case they forget, they also give it one more try from within the destructor. You know, just in case.

But then your IDisposable.Dispose() might have both managed and unmanaged objects to clean up, but the managed ones cannot be cleaned up when IDisposable.Dispose() is invoked from within the destructor, because they have already been taken care of by the garbage collector at that point in time, so there is this need for a separate Dispose() method that accepts a bool disposing flag to know if both managed and unmanaged objects should be cleaned up, or only unmanaged ones.

Excuse me, but this is just insane.

I go by Einstein's axiom, which says that things should be as simple as possible, but not simpler. Clearly, we cannot omit the cleaning up of resources, so the simplest possible solution has to include at least that. The next simplest solution involves always disposing everything at the precise time that it is supposed to be disposed, without complicating things by relying on the destructor as an alternative fall back.

Now, strictly speaking, it is of course impossible to guarantee that no programmer will ever make the mistake of forgetting to invoke IDisposable.Dispose(), but what we can do is use the destructor to catch this mistake. It is very simple, really: all the destructor has to do is generate a log entry if it detects that the disposed flag of the disposable object was never set to true. So, the use of the destructor is not an integral part of our disposal strategy, but it is our quality assurance mechanism. And since this is a debug-mode only test, we can place our entire destructor inside an #if DEBUG block, so we never incur any destruction penalty in a production environment. (The IDisposable.Dispose( bool disposing ) idiom prescribes that GC.SuppressFinalize() should be invoked precisely in order to lessen the overhead of finalization, but with my mechanism it is possible to completely avoid that overhead on the production environment.)

What it boils down to is the eternal hard error vs. soft error argument: the IDisposable.Dispose( bool disposing ) idiom is a soft error approach, and it represents an attempt to allow the programmer to forget to invoke Dispose() without the system failing, if possible. The hard error approach says that the programmer must always make sure that Dispose() will be invoked. The penalty usually prescribed by the hard error approach in most cases is assertion failure, but for this particular case we make an exception and lessen the penalty to a simple issuance of an error log entry.

So, in order for this mechanism to work, the DEBUG version of our application must perform a full garbage disposal before quitting, so as to guarantee that all destructors will be invoked, and thus catch any IDisposable objects that we forgot to dispose.

  • Now, strictly speaking, it is of course impossible to guarantee that no programmer will ever make the mistake of forgetting to invoke IDisposable.Dispose() Actually, it's not, though I don't think C# is capable of it. Don't expose the resource; instead provide a DSL for describing everything you'll do with it (basically, a monad), plus a function that acquires the resource, does the things, frees it, and returns the result. The trick is to use the type system to guarantee that if someone smuggles out a reference to the resource, it can't be used in another call to the run function. – Doval Mar 19 '15 at 17:41
  • 2
    The problem with Dispose(bool disposing) (which is not defined on IDisposable is that it is used to deal with cleaning up of both managed and unmanaged objects the object has as a field (or is otherwise responsible for), which is solving the wrong problem. If you wrap all unmanaged objects in a managed object with no other disposable objects to worry about then all Dispose() methods will either be one of those (have the finaliser do the same clean-up if necessary) or only have managed objects to dispose (don't have a finaliser at all), and the need for bool disposing disappears. – Jon Hanna Mar 22 '15 at 12:43
  • -1 bad advice because of how finalization actually works. I utterly agree with your point on the dispose(disposing) idiom being terribad, but I say such because people so often use that technique and finalizers when they only have managed resources (DbConnection object for instance is managed, it's not pinvoked or com marshalled), and YOU SHOULD ONLY EVER IMPLEMENT A FINALIZER WITH UNMANAGED, PINVOKED, COM MARSHALLED, OR UNSAFE CODE. I detailed above in my answer how woefully expensive finalizers are, do not use them unless you have unmanaged resources in your class. – Jimmy Hoffa Nov 13 '15 at 15:49
  • 2
    I almost want to give you +1 though just because you're decrying something so many people take as a core important thing in the dispose(dispoing) idiom, but the truth is that's only so prevalent because people are so afraid of GC stuff that something as unrelated as that (dispose should have zilch to do with GC) merits them to just take the prescribed medicine without even investigating it. Good on you for inspecting it, but you missed the biggest whole (it encourages finalizers farrr more often than they should be) – Jimmy Hoffa Nov 13 '15 at 15:52
  • 1
    @JimmyHoffa thank you for your input. I agree that a finalizer should normally only be used for releasing unmanaged resources, but wouldn't you agree that on the DEBUG build this rule is inapplicable, and that on the DEBUG build we should be free to use finalizers to catch bugs? That's all that I am suggesting here, so I do not see why you take issue with it. See also programmers.stackexchange.com/questions/288715/… for a lengthier explanation of this approach on the java side of the world. – Mike Nakis Nov 13 '15 at 16:12
0

Can you tell me in what sort of scenario it is actually a good or reasonable idea to force garbage collection? I'm not asking for C# specific cases but rather, all programing languages that have a garbage collector. I know that you can't force GC on all languages, like Java, but let's suppose you can.

Just speaking very theoretically and disregarding issues like some GC implementations slowing things down during their collection cycles, the biggest scenario I can think of to force garbage collection is a mission-critical software where logical leaks are preferable to dangling pointer crashes, e.g., because crashing at unexpected times might cost human lives or something of this sort.

If you look at some of the shoddier indie games written using GC languages like Flash games, they leak like crazy but they don't crash. They might take ten times the memory 20 minutes into playing the game because some part of the game's codebase forgot to set a reference to null or remove it from a list, and the frame rates might start to suffer, but the game still works. A similar game written using shoddy C or C++ coding might crash as a result of accessing dangling pointers as a result of the same type of resource management mistake, but it wouldn't leak so much.

For games the crash might be preferable in the sense that it can be quickly detected and fixed, but for a mission-critical program, crashing at totally unexpected times might kill somebody. So the main cases I think would be scenarios where not crashing or some other forms are security are absolutely critical, and a logical leak is a relatively trivial thing in comparison.

The main scenario where I think it's bad to force GC is for things where the logical leak is actually less preferable than a crash. With games, for example, the crash won't necessarily kill anyone and it might be easily caught and fixed during internal testing, whereas a logical leak might go unnoticed even after the product ships unless it's so severe that it makes the game unplayable within minutes. In some domains an easily-reproducible crash that occurs in testing is sometimes preferable to a leak that no one notices immediately.

Another case I can think of where it might be preferable to force GC on a team is for a very short-lived program, like just something executed from the command line which does one task and then shuts down. In that case the program's lifetime is too short to make any sort of logical leak non-trivial. Logical leaks, even for big resources, usually only become problematic hours or minutes after running the software, so a software that is only intended to be executed for 3 seconds is unlikely to ever have problems with logical leaks, and it could make things a lot simpler to write such short-lived programs if the team just used GC.

protected by maple_shaft Mar 18 '15 at 19:00

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.