finalize() *much* better than PhantomReferences (Re: Ad removing finalize eventually (Re: JEP 411 Headaches: Instrumenting private methods in the JDK for authorization checkpoints.

Rony G. Flatscher Rony.Flatscher at
Wed Aug 4 18:54:19 UTC 2021

On 04.08.2021 16:35, Rony G. Flatscher wrote:
> On 03.08.2021 15:08, Rony G. Flatscher wrote:
>> On 03.08.2021 11:16, Andrew Haley wrote:
>>> On 8/2/21 7:39 PM, Rony G. Flatscher wrote:
>>>> If the Java ScriptEngine goes out of scope at the Java side its peer
>>>> ooRexx interpreter instance should be terminated, the same is true
>>>> for peer objects for either side (Java peer objects for Rexx
>>>> objects, i.e. Rexx objects being boxed in Java proxy objects, and
>>>> vice versa, ooRexx peer objects for Java objects such that one can
>>>> send messages to an ooRexx peer to the Java side where with
>>>> reflection the appropriate methods get invoked in the corresponding
>>>> Java object).
>>> Sure, but Java doesn't know when a Java ScriptEngine goes out of scope.
>>> Here's the scenario: we're running a generational garbage collector.
>>> Young-generation collections are keeping the heap well within our
>>> limits, so there's no need to do full collections. The ScriptEngine
>>> has been running for a while, so is in the old generation. There's no
>>> reason it will ever be collected. That's why some sort of scoped
>>> access, such as try with resources, is the right way to go if you
>>> really need cleanups to be done.
>> This approach depends on a try-with block, which is easy and straight forward if e.g. reading from a
>> resource like a file, processing a SLQ statement etc. But "longevity service objects" are hard, if
>> not impossible, to be put into a (single, central) try-with block. This is especially true for peer
>> usages (e.g. in ooRexx an object created by one Rexx interpreter instance can be deployed on another
>> Rexx interpreter instance, and if it is a peer object the peer Java object must stay alive as long
>> as the ooRexx object is referenced).
>> From first, simple tests on Java 8 it seems however, that System.runFinalization() will invoke
>> finalize() on those Java objects that have no references at the time runFinalization() runs. So it
>> does not follow the general gc strategy you describe. Running System.gc() does not have that effect,
>> i.e. running finalize() of the very same Java objects without references at gc() time. So currently
>> PhantomReferences to these Java objects that have no references would not be put on the queue but at
>> some undefined later time, possibly never as you describe.
>> The question then would be whether it was possible to have runFinalization() also look out for
>> PhantomReference'd Java objects without references and put them on the queue at that point in time.
>> This of course assumes that runFinalization() not only is able to identify unreferenced Java objects
>> with a finalize method, but also unreferenced Java objects for which a PhantomReference got created
>> for. [If so, runFinalization() may remain helpful even after finalize() got eventually removed.]
> Just for the record: the observed behavior (finalize() of all unreferenced objects runs at
> runFinalization() time, PhantomReferences are not put on the queue at that time) is the same for
> Java 6, 8, 11 and 16.

O.K. now having implemented PhantomReferences to replace the usage of finalize() in the bridge it
turns out that finalize() gets invoked "magnitudes" of times (100 and more finazable objects in
different gc runs within a total run of five seconds) more often than their PhantomReferences are
being put on the reference queue (not a single one!) to allow to carry out the necessary finalizations!

This is without employing runFinalization(), just the normal garbage collection that takes place in
a running system. (These tests were run against unit tests of the ooRexx-Java bridge to make sure
that the tests work the same as before the code changes.)


Another test with JavaFX (the quite famous JavaFX address book example implemented in ooRexx) is
even interactively demonstrative: there were about ten, twelve finalize() runs while operating it
compared to not a single (!) reference being put on the reference queue in the same time frame!
Observing this one would wonder whether PhantomReferences would work at all (the implementation
works, it got tested separately).

[To be able to compare the behavior I left finalize() in place but only output a debug message with
a string-id while for the same objects PhantomReferences got created and any such reference removed
from the reference queue would create a debug message there with the string-id as well and run the
cleanup method, however this has never happened.]


So seriously, please consider to have PhantomReferences being serviced by the gc like finalizable()
objects, such that gc runs put PhantomReferences on the reference queue as early as possible.


P.S.: For the time being I will probably be employing finalize() as long as the Object.finalize()
method exists and switch over to PhantomReferences thereafter, hoping that by then the timings of
PhantomReferences being put on the reference queue by gc will be close or comparable to the current
timings a finalizable object gets its finalize() cleanup method invoked.

More information about the jdk-dev mailing list