views:

138

answers:

5

What guarantees are the for the garbage collector?

From my research I have managed to find:

  • If there is still a reference to the memory it will not be garbage collected
  • If there is no reference:
    • When it is GC is non deterministic
    • When the GC kicks in the finalizer will be run before memory is released.
    • There is no guarantee about the order of Finalizers (so do not assume parent will be run before child).

But what I really want to know is:

Is there a guarantee that all memory will eventually be garbage collected and the finalizer (destructor) run on the object (assuming the program exited nicely). For example an application with no memory pressure when it eventually exits will it force the GC to go find all objects and make sure the finalizer (destructor) is called (including static member variables)?

I did find a quote on this page: http://www.c-sharpcorner.com/UploadFile/tkagarwal/MemoryManagementInNet11232005064832AM/MemoryManagementInNet.aspx

In addition, by default, Finalize methods are not called for unreachable objects when an application exits so that the application may terminate quickly.

But I am not sure how authoritative this quote is.

I also found documentation on: CriticalFinalizerObject

+1  A: 

1.6.7.6 of the Spec says:

1.6.7.6 Destructors

A destructor is a member that implements the actions required to destruct an instance of a class. Destructors cannot have parameters, they cannot have accessibility modifiers, and they cannot be invoked explicitly. The destructor for an instance is invoked automatically during garbage collection.

The garbage collector is allowed wide latitude in deciding when to collect objects and run destructors. Specifically, the timing of destructor invocations is not deterministic, and destructors may be executed on any thread. For these and other reasons, classes should implement destructors only when no other solutions are feasible.

The using statement provides a better approach to object destruction.

So no, it's not guaranteed they are called.

Noon Silk
A: 

The only time that a finalizer won't be invoked at all is if an AppDomain is forcibly unloaded.
In general, you don't need to worry about it.

SLaks
I think you do need to worry about it, because if you try and do some substantial amount of work in your finalizer, it may not be allowed to finish.
Noon Silk
In general, you won't be doing a substantial amount of work in your finalizer.
SLaks
Obviously; but it seems logical to assume that's why he's asking; the answer is that using `IDisposable` is the best way to ensure deterministic cleanup of your class.
Noon Silk
I am having a discussion with a collegue over a code review I did of his code. He insists that the destructor is guranteed to be called on an object. I disagree (but am not sure) and would prefer the use of IDisposable.
Martin York
@Martin York: Use IDisposable when you are allocating Unmanaged resources.
Mitch Wheat
What is the finalizer doing, and how important is it?
SLaks
A: 

There is no guarantee.

There might be a guarantee if your process terminates nicely for some definition of nicely. But there are so many things not nice that can happen:

  • power failure
  • process terminated in a 'hard' or 'forced' way
  • unmanaged thread throwing calling OS exit() function or throwing an exception
  • call to System.Environment.FailFast, which does:

MSDN: "Terminates a process but does not execute any active try-finally blocks or finalizers."

Tim Lovell-Smith
'power failure' pretty much means all memory gets released!
Mitch Wheat
I understand that when bad things happen that nothing can be guranteed. I want to know the gurantees under normal application execution. where main() exits normally with no exceptions no signals have forced a halt etc.
Martin York
You have to realize that the OS will free all resources a process is using when it exits. Even if it's crashing/being killed.If you worry about leaving some files in an unclean state: you have the same problem with power failure. Use a database or similar transaction system to solve those issues.I can't think of a single situation where running finalizers on application exit would actually matter.
Daniel
@Daniel: What about when communicating with another PC, although in this situation, the other PC should be setup to handle if communications suddenly cut out, in which case the only problem will be if it blocks that PC from continuing properly for a period of time while it waits to see if its just lag or a proper disconnect (though this will probably only be a problem in UDP like communications whereas the OS can tell TCP connections to inform any connected PCs of the connection loss)
Grant Peters
+3  A: 

The only time you should write a finalizer is when you are building a type to handle a new kind of unmanaged resource. For example, a data access layer that uses Sql Server in a business app doesn't need a finalizer anywhere, even though there are unmanaged database connections involved, because the basic SqlConnection class will already finalize those connections if needed. But if you're building a brand new database engine from scratch that has connection limits similar to sql server's and are implementing the ado.net provider for it, that connection type should implement a finalizer to be as sure as possible that your connections are released.

But you don't get any guarantees beyond what happens when a process ends.

Update:

Given this context:

I am having a discussion with a collegue over a code review I did of his code. He insists that the destructor is guranteed to be called on an object. I disagree (but am not sure) and would prefer the use of IDisposable.

You are right to criticize the use of a destructor/finalizer. As I said above, you should only use them when working with an unmanaged resource that is genuinely new. Not just that instance of the resource, but the kind of resource you are working with.

For code that wraps "normal" unmanaged resources (things like SqlConnection, TcpClient, etc), IDisposable is a better choice. Then you know the resource will be cleaned up as soon as Dispose() is called rather than needing to wait for the type to be collected. If no one calls Dispose() (which is likely your colleague's concern), by the time your new type can be collected the instance of the original type for the unmanaged resource you are wrapping should be able to be collected as well, and it's finalizer will release the resource.

The main thing you need to bring to the table is that the finalizer cannot be called until the object is collected. You have to wait on the garbage collector, meaning you may be holding the resource open even longer. IDisposable allows you to release it right away. Of course you could do both, but that doesn't sound like what's going on here, and if you do have both you have to be careful not to conflict with the original type's finalizer or you could cause unwanted and harmful exceptions. And really, your own finalizer implementation is just redundant here and adds needless complexity.

Finally, I have to take issue with this statement:

If there is still a reference to the memory it will not be garbage collected

There can be references to an object and it will still be collected. What matters is if the object is reachable: are any of the references to the object rooted. For example, you may have a list with several objects in it. The list goes out of scope. Obviously there is still a reference to all of the objects in the list, but they can still all be collected in the first pass of the GC because the reference is no longer rooted.

Joel Coehoorn
and even reachable objects can be GC'd, as long as the only way to reach it is through a "WeakReference" object
Grant Peters
The exception the proves the rule.
Joel Coehoorn
If I remember correctly, when an object needs finalizing the GC marks it as such and stops. This leaves the object, as well as all obejcts being anchored by that object. So a single object that requires finalization could hold a large tree of objects in memory until the object is finalized. After that, the object will still not be collected until the next GC run. So if the object is Gen2 and your app is running smooth it could be a long time.
Glenn Condron
All good advice. But does not actually answer the question.
Martin York
+2  A: 

Is there a guarantee that all memory will eventually be garbage collected and the finalizer (destructor) run on the object (assuming the program exited nicely).

No. From the Object.Finalize documentation it is clear that finalizers may not be invoked if

  • Some other finalizers don't finish properly:

    Another finalizer blocks indefinitely (goes into an infinite loop, tries to obtain a lock it can never obtain and so on). Because the runtime attempts to run finalizers to completion, other finalizers might not be called if a finalizer blocks indefinitely.

  • Some other finalizers create more finalizable objects, making it impossible to finalize all finalizable objects:

    The runtime continues to Finalize objects during shutdown only while the number of finalizable objects continues to decrease.

  • Some other finalizers throw exceptions:

    If Finalize or an override of Finalize throws an exception, and the runtime is not hosted by an application that overrides the default policy, the runtime terminates the process and no active try-finally blocks or finalizers are executed. This behavior ensures process integrity if the finalizer cannot free or destroy resources.

That being said, there are more reasons why you wouldn't want to use finalizers unless strictly necessary.

  • They slow down the garbage collector (even making it possible to slow it down so much that memory is not reclaimed as fast as it is used up).
  • They run on another thread, bringing multi-threading issues into play.
  • They're not executed in a deterministic order.
  • They can resurrect objects which were already finalized (and which won't be finalized again unless explicitly re-registered for finalization).
Wim Coenen