问题
Here is example program that exhibits surprising finalization behavior:
class Something
{
public void DoSomething()
{
Console.WriteLine("Doing something");
}
~Something()
{
Console.WriteLine("Called finalizer");
}
}
namespace TestGC
{
class Program
{
static void Main(string[] args)
{
var s = new Something();
s.DoSomething();
GC.Collect();
//GC.WaitForPendingFinalizers();
s.DoSomething();
Console.ReadKey();
}
}
}
If I run the program, what gets printed is:
Doing something
Doing something
Called finalizer
This appears as expected. Because there is a reference to s after the call to GC.Collect()
, s is not a garbage.
Now remove comments from the line //GC.WaitForPendingFinalizers();
build and run the program again.
I would expect nothing to change in the output. This because I read that if object is found to be a garbage and it has a finalizer, it will be put on finalizer queue. Since object is not a garbage, then is seems logical that it should not be put on finalizer queue. Thus the line commented out should do nothing.
However, the output of the program is:
Doing something
Called finalizer
Doing something
Can somebody help my understanding of why finalizer gets called?
回答1:
I can't reproduce the problem on my laptop, but your DoSomething
method doesn't use any fields within the object. That means that the object can be finalized even while DoSomething
is running.
If you change your code to:
class Something
{
int x = 10;
public void DoSomething()
{
x++;
Console.WriteLine("Doing something");
Console.WriteLine("x = {0}", x);
}
~Something()
{
Console.WriteLine("Called finalizer");
}
}
... then I suspect you will always see DoingSomething
printed twice before "Called finalizer" - although the final "x = 12" may be printed after "Called finalizer".
Basically, finalization can be somewhat surprising - I very rarely find myself using it, and would encourage you to avoid finalizers wherever possible.
回答2:
Jon's answer is of course correct. I would add to it that the C# specification calls out that the compiler and runtime are allowed to (but not required to) notice that the reference contained in a local variable is never again dereferenced, and at that point the garbage collector is allowed to treat the object as dead if the local is the last living reference. Therefore the object can be collected and the finalizer run even though there appears to be a reference in a living local variable. (And similarly the compiler and runtime are allowed to make locals live longer if they so choose.)
Given this fact you can end up in bizarre situations. For example, it is possible for a finalizer to be executing on the finalizer thread while the object's constructor is running on a user thread. If the runtime can determine that "this" is never dereferenced again then the object can be treated as dead the moment the constructor is done mutating fields of "this". If the constructor then does additional work -- say, mutating global state -- then that work can be done as the finalizer is running.
This is just yet another reason why writing a correct finalizer is so difficult that you probably should not do so. In a finalizer everything you know is wrong. Everything you're referring to could be dead, you're on a different thread, the object might not be fully constructed, possibly none of your program invariants are actually maintained.
回答3:
Your DoSomething()
is so trivial that it's likely to get inlined. After it's been inlined, there's nothing that still has a reference to the object, so there's nothing preventing it from being garbage collected.
GC.KeepAlive() is designed specifically for this scenario. It can be used if you want to prevent an object from being garbage collected. It does nothing, but the garbage collector doesn't know that. Call GC.KeepAlive(s);
at the end of Main
to prevent it from being finalised earlier.
来源:https://stackoverflow.com/questions/20731667/why-is-finalizer-called-on-object