Are the child objects still alive when Object.Finalize is called by the GC?
Let's say I have this class:
class Test
{
readonly object _child = new Object();
// ...
~Test()
{
// access _child here
// ...
}
}
Is an object _child
guaranteed to be alive when ~Test
called by the garbage collector? Or should I "bind" _child
to GCHandle.Alloc
in the constructor first?
source to share
As a field readonly
_child
, you cannot lose your reference (unless you set it to null by reflection). This means that until Test
garbage is collected, it _child
will remain in memory for sure.
Also, you are using Finalizer
which is called before garbage collection. Only on the next pass will the object's memory be restored, at the moment it _child
will be alive. In other words, when a method Finalize
is called _child
, it will be available and safe to access.
The Finalizer is called does not mean that the memory will be reclaimed. If you do something like the following Finalize
, it will be called, but the memory will not be fixed
class Test
{
readonly object _child = new Object();
private static Test evilInstance;
~Test()
{
evilInstance = this;//Do something crazy
//This resurrects this instance, so memory will not be reclaimed.
}
}
Finalizers are almost never needed when you are dealing with managed code. It adds extra work to the garbage collector, and strange things can happen as we saw above.
Update . If you're _child
only using for lock
, it's safe to use because the instance _child
won't be empty, which means it points to a valid link. Monitor.Enter
and Monitor.Exit
just take care of links that are completely safe to use (block only).
What if you want the child finalizer to be called only after the
Test's
finalizer is called?
There is a workaround: you can inherit a class Child
from SafeHandle
and that does the trick. It will make sure both Test
and Child
go out of scope at the same time, it will call the Test's
finalizer first since it Child
inherits from SafeHandle
, which delays its completion. But IMO is not dependent on this. Because other programmers working with you may not know this, which leads to confusion.
This critical finalizer also has a weak order guarantee, stating that if the normal end object and the critical end object become unavailable at the same time, then the normal object finalizer is fired first.
Quote from SafeHandle: An example of using reliability
source to share
The most pertinent explanation so far IMHO can be found here (see Carlsen's answer), and so to summarize the OP's answer:
Any accessible (child or external) object during object finalization will still be available in memory, but the state of such an object will depend on whether that object has already been finalized by itself, since there is no specific order of completion between objects in the finalization queue.
In short, quoting the post:
You cannot access the objects referenced by your object with finalizers, since you have no guarantee that those objects will be in a healthy state when your finalizer is running. Objects will still be present in memory and not collected, but they can be closed, completed, completed, etc. Already.
However, you can safely use any other accessible object (without finalization) during the object completion stage, for example. line. In practice, this means that it is safe to use any accessible object that does NOT implement the IDisposable interface.
source to share
I created a simple test:
using System;
namespace ConsoleApplication1
{
class Program
{
class Child
{
public override string ToString()
{
return "I am a child!";
}
~Child()
{
Console.WriteLine("Child finalized");
}
}
class Test
{
readonly object _child = new Child();
readonly WeakReference _ref;
public Test()
{
_ref = new WeakReference(_child);
}
// ...
~Test()
{
Console.WriteLine("Test finalized, child: "
+ _child.ToString() + ", is alive: "
+ _ref.IsAlive);
}
}
static void Main(string[] args)
{
var test = new Test();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
Console.ReadLine();
}
}
}
Release build:
Child finalized Test finalized, child: I am a child !, is alive: False
So it _child
ends up first. So, I need to be done GCHandle.Alloc(_child)
in the constructor and done GCHandle.Free
in the finalizer after I'm done with _child
.
To answer the comment, why do I need all this: _child
refers to a method that is called from the finalizer to access an unmanaged resource. This method does lock (_child) { ... }
, and can also be called from outside the finalizer.
source to share