What is the up-front cost of an object being final

2020-08-20 08:34发布

问题:

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 that obj 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?

回答1:

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:

  • their construction does not require transition to VM and back and can be inlined;
  • they do not have extra fields besides inherited from java.lang.ref.Reference;
  • no global synchronization is made.

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