Finalizers are tricker than you might think. Part 2
sergeyteplyakov.github.io
sergeyteplyakov.github.io
Going back to finalizers, while they seemed a great idea since Smalltalk days, they have proven otherwise, and that is why nowadays they are largely deprecated.
In Java, and as of last Go's version, there are alternative ways with classical finalizers being "deprecated for removal" in Java's case, and on .NET SafeHandles and IDispose pattern is the way.
One of the main issues with finalize method is that it adds the newly created objects (prior to invoking the c-tor) to a (effectively) global queue which has rather negative perf. impact. The happens-before semantics also work in a weird way, e.g. fields set in the c-tor may not be visible in finalize().
Finalize/clean up code is still needed to ensure proper freeing of non-managed 'objects'. They are more of a defense mechanism in most cases as close/dispose is the correct way.
It's a well known way to deal with such cases by using an additional weak reference. (even I think J. Bloch has mentioned it). Here (typing w/o an other editor, excuse typos):
class X<T> extends PhantomRefenece<T> {
final WeakReference<T> weak;
X(T ref, ReferenceQueue<? super T> q){
super(ref, q);
this.weak = new WeakReference<>(ref);
}
}
...
for (X x; (x = (X)q.poll()) != null;) {
process(x.weak.get());
}
This is why a mentioned it is effectively pre-mortrem. PhantomReference still keeps the referent, it's just not possible to retrieve it as get() is overridden.