Discussions of finalizable objects in Java typically discuss the common indirect costs that happen when finalizable objects (and their associated resources) cannot be quickly garbage collected.
I'm more interested, at the moment, in what the actual direct cost of being finalizable is, both in memory terms, and in object allocation time. I've seen oblique references to the existence of such a cost in a number of places, for example, Oracle's article on finalization memory retention issues notes:
When
obj
is allocated, the JVM internally records thatobj
is finalizable. This typically slows down the otherwise fast allocation path that modern JVMs have.
How does the JVM record that an object instance is finalizable, and what are the memory and performance costs of doing so?
For those interested in my specific application:
We produce and retain millions of incredibly lightweight objects; adding a single pointer to these objects is incredibly costly, so we've done a fair bit of work to remove pointers from them, instead using smaller numeric ids packed into a subset of the bits of a field. Unpacking the number allows the shared immutable property with that id to be retrieved from a Pool that stores them using a Map.
The remaining question is how to handle the garbage collection of property values that are no longer used.
One strategy that has been considered is using reference counting; when objects are created and retrieve the pooled id for a value, the reference count for that value is incremented; when it is no longer used, it must be decremented.
One option to ensure this decrement happens is to add the following finalize method:
public void finalize() {
Pool.release(getPropertyId());
}
However, if the very act of being finalizable means that an additional pointer to the object must be kept, the up-front cost of being finalizable would be considered high for this application. If it means additional objects must be allocated, it would almost certainly be too high...hence, my question: what is the direct up-front cost of being finalizable?
Finalizers are awful not only because of retention issues, but from performance perspective, too.
In Oracle JDK / OpenJDK the objects with finalize
method are backed by the instances of Finalizer, the subclass of java.lang.ref.Reference
.
All Finalizers are registered at the end of object's contructor in two steps: a call from Java to VM followed by the invocation of Finalizer.register(). This double transition Java->VM->Java cannot be inlined by JIT compiler. But the worst thing is that Finalizer's constructor makes a linked list under the global lock! (facepalm)
Finalizers are also bad in terms of memory footprint: in addition to all Reference fields they have two extra fields: next
and prev
.
PhantomReferences are much better than finalizers:
java.lang.ref.Reference
;This benchmark compares the allocation speed of finalizable objects and the objects backed by PhantomReference:
Benchmark Mode Cnt Score Error Units
Finalizer.finalizable thrpt 5 2171,312 ± 1469,705 ops/ms
Finalizer.phantom thrpt 5 61280,612 ± 692,922 ops/ms
Finalizer.plain thrpt 5 225752,307 ± 7618,304 ops/ms