C# language: Garbage Collection, SuppressFinalize
Asked Answered
F

5

17

I'm reading "The C# Language", 4th Edition, it talks about garbage collection as below:

"BILL WAGNER: The following rule is an important difference between C# and other managed environments.

Prior to an application’s termination, destructor's for all of its objects that have not yet been garbage collected are called, unless such cleanup has been suppressed (by a call to the library method GC.SuppressFinalize, for example)."

So I have a couple of questions here:

  • Q1. Why .net is different from other managed environments (I suppose this is hinting Java?) here? Any particular design concern?

  • Q2. What will happened to objects that GC.SuppressFinalize is called? I understand that this means GC will not call such objects' finalizer (destructor), if so, when will these objects got really destroyed, so that the allocated memory bits are returned to the heap? Otherwise there'll be Memory Leak?

Fairhaired answered 11/7, 2011 at 14:53 Comment(4)
Memory leaks aren't something you need to worry about during application termination - the whole address space is about to be cleaned up.Gumdrop
Finalization and deallocation are distinct. An object not getting "finalized" doesn't mean it won't get deallocated.Unpolled
@Damien_The_Unbeliever: If a network protocol uses UDP to simulate a "lossy" stream and an application initiates a connection using such a protocol and never cleans it up, the remote system will have no way of knowing that the connection is no longer needed and any resources dedicated to that connection should be made available for other purposes. Eventually the remote system may time out, but allowing an object which encapsulates such a connection to notify a remote system before it is abandoned will be much cleaner.Goggleeyed
I'm not sure why you're resurrecting on this question at this time, nor why you've picked me out for this specific point. Your new comments seems to be nothing to do with memory leaks, which was the part of the question I was attempting to address with my comment (I think, you're forcing me to jump back to a context I was last in 2 1/2 years ago)Gumdrop
D
40

What will happened to objects that GC.SuppressFinalize is called? I understand that this means GC will not call such objects' finalizer (destructor), if so, when will these objects got really destructed? otherwise there'll be Memory Leak right?

You have a misunderstanding of what finalization is for. Finalization is for cleaning up resources that are not managed memory.

Suppose you have an object of reference type that contains an integer field. That integer field just happens to be a handle to a file that was obtained by calling into unmanaged code to open the file.

Since some other program might want to access that file, it is polite to close the file as soon as possible. But the .NET runtime has no idea that this integer has any special meaning to the operating system. It's just an integer.

The way you solve this problem typically is you mark the object as implementing IDisposable, and then you call "Dispose" on the object as soon as you're done with it. Your implementation of "Dispose" then closes the file.

Note that there is nothing special going on here. It is just a convention that a method that cleans up an unmanaged resource is called "Dispose" and an object that needs to be disposed implements IDisposable. The garbage collection knows absolutely nothing about this.

So now the problem arises: what if someone forgets to call Dispose? Does the file stay open forever? (Clearly the file will be closed when the process ends, but what if the process runs for a long time?)

To solve this problem, you use a finalizer. How does that work?

When an object is about to be garbage collected, the garbage collector checks it to see if it has a finalizer. If it does, then instead of garbage collecting it, it puts it on the finalizer queue. At some unspecified point in the future, a thread runs that examines the queue and calls a special "Finalize" method on every object. After that, the object is removed from the finalization queue and marked as "hey, I've already been finalized". The object is now once again eligable for collection, and so the garbage collector eventually runs and collects the object without putting it on the finalization queue.

Clearly "Finalize" and "Dispose" frequently need to do the same thing.

But now another problem arises. Suppose you dispose an object. Now it does not need to be finalized. Finalization is expensive; it keeps a dead object alive for much longer than it needs to be. Therefore, traditionally when one disposes an object, the implementation of Dispose not only closes the unmanaged resource, it also marks the object as "this object has already been finalized, don't finalize it again". That way it tricks the garbage collector into not putting the object on the finalization queue.

So let's answer your specific questions:

What will happened to objects that GC.SuppressFinalize is called?

When the object is dead the garbage collector will simply reclaim the memory of the object without putting the object on the finalizer queue.

I understand that this means GC will not call such objects' finalizer

The GC never calls a finalizer. The finalizer thread is the only thing that calls finalizers.

when will these objects got really destructed?

It is not clear what you mean by "destructed". If you mean "when will the finalizers run?" the answer is "never" because you said to suppress finalization. If you mean "when will the memory in the managed heap be reclaimed?", the answer is "as soon as the object is identified as dead by the garbage collector". That will happen sooner than normal because the object will not be kept alive by the finalizer queue.

Duckboard answered 11/7, 2011 at 15:11 Comment(10)
+1 for writing this before me. (and your answer is probably better than mine would have been)Chantalchantalle
thanks a lot Eric, now I understand, so there's a pattern for unmanaged resource; there's a garbage collector and a finalization queue, they passing objects like juggling balls; only finalizer thread calls finalizers; GC has a SuppressFinalize, both this and finishing of finalizer mark an object as subject to immediate deallocation. Yeah I got it!Fairhaired
@athos: Well, almost. There are subtleties involved. For example, suppose the finalizer does something really stupid, like sets a static field to reference the object that is being finalized. The object is now alive again and cannot be "subject to immediate deallocation". Finalizers that bring objects back from the dead are super dangerous, but it is legal. There is not a guarantee that an object that has just been finalized is going to be collected any time soon. In fact, finalization can cause an object to move to a later generation, delaying its collection.Duckboard
ok.. so if a finalizer resurrects the object, this might lead to an infinite repeating of finalization... hmmm, this is nasty...Fairhaired
@athos: no, I never said that! You are assuming a mechanism that doesn't exist. When the object is on the finalizer queue, it is alive. Once it is finalized it is marked as "don't finalize me", and then is just an ordinary object, either alive or dead, to be determined by the next garbage collection. When it is eventually determined to be dead, the GC does not put it on the finalizer queue again.Duckboard
In short: the GC's job is to determine (1) if an object is alive or dead, and (2) whether finalization of a dead object is suppressed or not. If an object is determined to be dead and needs finalization, it becomes alive again on the finalizer queue. After it is finalized, it is marked as "suppress finalization". When the GC determines it to be dead again -- which could be a while if the finalizer resurrected the object -- the GC reclaims its storage without attempting to finalize it again.Duckboard
@Eric - the finalizer could also call ReRegisterForFinalize, in which case the object really could be finalized an arbitrary number of times.Calmative
@kvb: Absolutely, and yes, I probably should have mentioned that. My point was that simply resurrecting the object does not automatically make it re-finalizable.Duckboard
Great summary. I wish this was the first thing I had read when I was first learning about finalizers and IDisposable.Sunwise
@Eric Lippert: Am I correct in thinking that the only way for an object in a class that uses resurrection to know whether any WeakReferences to it might exist and remain valid is for it to have a constructor which is only available to other classes that won't expose any references to the outside world nor keep any references the object doesn't know about?Goggleeyed
C
5

Q1: I suspect it's because it cares more about achieving great performance, as opposed to Java which has made quite a few sacrifices for simplicity.

Q2: Since finalizers aren't even guaranteed to be called in the first place (even if SuppressFinalize didn't exist), this should be only used for performance reasons, when you've disposed the resources already. Otherwise you should use IDisposable to dispose of resources.

Finalization != Destruction

Destructors (in the C++ sense) don't exist in .NET -- because every object, in a sense, has a "destructor", called the garbage collector. :)
What C# calls "destructors" are in fact finalizers. Finalizers are for disposing things other than the memory allocated by the object, such as file handles, etc. Not every object, therefore, had a finalizer. The memory is always freed by the GC, so you don't get a memory leak that way.

Carpentry answered 11/7, 2011 at 15:1 Comment(5)
Destructors does not exist in .net? That's just false msdn.microsoft.com/en-us/library/66x5fx1b.aspxTops
@Oskar: Not in the same sense as C++, which also deallocates the object itself. The name is the same, but not the entire concept (e.g. they're not guaranteed to be called). Don't take my words out of context please. :(Carpentry
It does not matter in what sense you meant. You said they don't exist but they do ;)Tops
@Oskar: Like I said, I think you forgot your void *context parameter... ;)Carpentry
Hahaha. I just reacted on the sentence. Looks better now :)Tops
R
2

I only know the second answer: SuppressFinalize is called when the object has already been destructed, i.e., by IDisposable.Dispose. So it shouldn't be destructed again.

This is because finalizers are a resource cleanup that occurs at a non-deterministic time. IDisposable was added to allow resource cleanup that occurs at a specific, predictable time.

EDIT: At process termination, memory leaks are immaterial. When a process terminates, its heaps are collected by Windows, so it doesn't matter if an object's memory is returned to the heap or not.

Rustic answered 11/7, 2011 at 14:55 Comment(4)
Read the answer again. "Destructed" in the sense of Dispose.Rustic
OK, maybe I shall be more clear in the wording, let me update the question.Fairhaired
I added an edit describing why memory leaks are not an issue during process termination.Rustic
Thanks Stephen. and everyone. Too late tonight, I'll read this tomorrow :) good nightFairhaired
L
1

I try to answer Q2:

If your class has a finalizer (shown by the ~ClassName), then it will not be directly collected by the garbage collection, instead it will be put on the finalizer queue which will be called later. Now when finalizer is finally called it usually frees any unmanaged resources, the class created. If for any reason the user already did it, usually by calling dispose, the finalizer don't need to clean the unmanaged memory, thus calling SuppressFinalizer is necessary. Otherwise it would try to free the resources again. So the only memory leak you will get is by any resources that you requested on creation of your class. The finalizer is just to free everything that is not already managed by the framework.

Lysin answered 11/7, 2011 at 14:58 Comment(0)
L
0

There is because once call GC.Collect() The object with finalize will move to next generation at least once.

We can call GC.SuppressFinalize, and GC will know this object is de-reference, we can remove this object and compact heap then.

Usually, this implement with dispose pattern

Lisbethlisbon answered 10/1, 2018 at 9:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.