JEP 132: More-prompt finalization (Re: Another take on Finalization)

Rezaei, Mohammad A. Mohammad.Rezaei at gs.com
Mon Jun 8 16:53:25 UTC 2015


Shouldn't we extend the same notion to other reference types? For Weak/Soft references, the (rough) equivalent would be a method of the kind:

public void markUnqueueable() // suggestions for better method name most welcome!
{
	this.q = ReferenceQueue.NULL;
}

This is quite useful when the reference is cleaned up before the referent is GC'ed, which happens, for example, when rehashing a weak map.

Thanks
Moh

>-----Original Message-----
>From: core-libs-dev [mailto:core-libs-dev-bounces at openjdk.java.net] On Behalf
>Of Peter Levart
>Sent: Saturday, June 06, 2015 11:03 AM
>To: Jonathan Payne
>Cc: 'hotspot-gc-dev at openjdk.java.net openjdk.java.net'; 'core-libs-
>dev at openjdk.java.net'
>Subject: Re: JEP 132: More-prompt finalization (Re: Another take on
>Finalization)
>
>Hi Jonathan,
>
>On 06/05/2015 11:11 PM, Jonathan Payne wrote:
>> Hi.
>>
>> I have had an interest in the topic of finalization ever since it caused me
>to abandon the G1 collector 3 or 4 years ago.
>>
>> I've recently implemented a fix for my interpretation of the problem, which
>might be very different from the discussion currently ongoing in the thread
>entitled "JEP 132: More-prompt finalization".
>>
>> My problem was that finalization was not being run at all with the G1
>collector. Not at all. That would have been fine with me because none of the
>existing objects in the Finalizer queue actually needed the service anymore:
>the files, sockets, streams, etc. had all been properly closed by my
>application, otherwise the server would have long since failed completely.
>However, those objects started to accumulate in the VM and eventually (8 hours
>later) brought the server down.
>
>The most probable cause of the described behavior might be that your
>finalizable objects live long enough so that they are promoted to the
>old-generation together with their tracking Finalizer(s)
>(FinalReferences). We know that a finalizable object must undergo at
>least 2 GC cycles to be reclaimed. The 1st cycle merely discovers the
>final-reachable objects and links their FinalReference(s) to the pending
>chain. ReferenceHandler thread then pushes them to the finalization
>queue where their final() methods are invoked by finalizer thread which
>also clears their associated FinalReference(s) and unlinks them from the
>double-linked list. During the 2nd cycle, such finalizable objects are
>usually found unreachable and are reclaimed. It might be that G1 has (or
>had) a performance problem so that it couldn't discover final-reachable
>objects in old-generation soon enough to push them through 2 cycles
>before the system choked.
>
>This is roughly similar to a problem you might have if lots of normal
>objects get promoted to old-generation, but it might be the effects are
>more drastic in G1 when those objects are finalizable. So you might be
>able to tune your app by increasing the young generation and/or the time
>it takes before objects are promoted to old-generation.
>
>I understand that it would be desirable for a finalizable object to be
>made "untracked" as soon as it is manually cleaned-up. This would most
>certainly give a relief to GC as it could reclaim such untracked objects
>immediately like normal objects without pushing them through all the
>finalization steps.
>
>Such feature would need new API. Here's my take on such API incorporated
>in my prototype. This feature is mostly independent of other changes in
>the prototype and could be provided stand-alone. I choose to incorporate
>it into the prototype to compare it's overhead with classical
>finalization in unchanged and changed JDKs:
>
>http://cr.openjdk.java.net/~plevart/misc/JEP132/ReferenceHandling/webrev.03/
>
>The java.lang.ref.Finalizator is a public subclass of package-private
>Finalizer which is a subclass of package-private FinalReference which is
>a subclass of public Reference. The public API therefore consists just
>of two types: Reference and Finalizator and the public methods either
>implemented or inherited by Finalizator. Finalizator is basically just a
>special kind of Reference, which can't be subclassed (is final), can't
>be registeread with a custom reference-queue, and can only be
>constructed using a Finalizator.create(finalizee, thunk) factory method
>taking a 'finalizee' to be tracked and a 'thunk' that is usually just an
>adapter for invoking a private cleanup method on the finalizee.
>Finalizator also implements java.lang.Runnable. It's run() method is
>invoked by finalization infrastructure or manually by user code that
>wishes to promptly trigger clean-up (from AutoCloseable.close() method
>for example).
>
>Here's how a classical finalizable class that also implements
>AutoCloseable might be implemented. Note that the class must implement
>it's own logic to make clean-up idempotent, since finalize() will be
>called even after close() has manually or automatically already been called:
>
>     static final class Finalizable extends AtomicBoolean implements
>AutoCloseable {
>
>         @Override
>         protected void finalize() throws Throwable {
>             close();
>         }
>
>         @Override
>         public void close() {
>             // close must be idempotent
>             if (compareAndSet(false, true)) {
>                 // clean-up invoked at most once
>             }
>         }
>     }
>
>
>And here's how an alternative celanup might be implemented using
>Finalizator. Finalizator already guarantees that it's 'thunk' will be
>called at most once regardless of whether it was triggered by GC and/or
>manually:
>
>
>     static final class Destroyable implements AutoCloseable {
>         final Finalizator<Destroyable> finalizator =
>             Finalizator.create(this, Destroyable::destroy);
>
>         void destroy() {
>             // clean-up invoked at most once
>         }
>
>         @Override
>         public void close() {
>             // close just runs the finalizator
>             finalizator.run();
>         }
>     }
>
>
>As soon as Finalizator is run() 1st time, it is cleared and unlinked
>from the doubly-linked list. After that, GC can reclaim it and the
>finalizee right away without pushing them through the discovery and
>reference processing pipeline only to unlink the Finalizer from the
>doubly-linked list.
>
>I have done some testing and the results of creating and destroying 100M
>objects with a sustained rate of ~90 objects/ms with or without
>performing (AutoCloseable) clean-up immediately after construction gives
>the following results:
>
>
>Finalization, ORIGINAL
>
>real    2m5.958s
>user    0m33.855s
>sys     0m1.982s
>
>
>AutoCloseable combined with Finalization, ORIGINAL
>
>real    2m0.952s
>user    0m32.103s
>sys     0m1.730s
>
>
>Finalization, PATCHED
>
>real    2m0.519s
>user    0m16.664s
>sys     0m1.240s
>
>
>AutoCloseable combined with Finalization, PATCHED
>
>real    1m55.641s
>user    0m16.872s
>sys     0m1.218s
>
>
>Finalizator-based cleanup, PATCHED
>
>real    2m1.379s
>user    0m17.422s
>sys     0m1.321s
>
>
>AutoCloseable combined with Finalizator-based cleanup, PATCHED
>
>real    1m55.169s
>user    0m4.167s
>sys     0m1.139s
>
>
>
>We see what I have already shown before that my prototype practically
>halves the CPU overhead of finalization infrastructure. Just making an
>object AutoCloseable and promptly doing the clean-up can not reduce this
>overhead if the object is also finalizable. But if manual clean-up also
>"unregisters" the Finalizator from the doubly-linked list and clears it,
>it spares the finalization infrastructure from processing it as a
>finalizable object which further reduces the CPU overhead for a factor
>of 4, totaling just 1/8th of overhead of classic finalization with
>current JDK. Besides greatly reduced CPU overhead, such objects are also
>potentially more promptly reclaimed by GC, freeing memory for other more
>useful things...
>
>
>> Which brings me to a few points:
>>
>> Finalization as conceived in the early JDKs was a bad idea. To make matters
>worse, the way we then made use of it in those early days was A REALLY REALLY
>bad idea.
>> None of this mattered in those days because the GC ran often and quickly and
>finalization occurred during every GC cycle.
>> There may be situations where finalization as a feature actually matters,
>but in the intervening years the JDK has added new technologies that provide a
>way to accomplish finalization on your own, in your own code. A few helper
>classes and it might even be easy when it's necessary, which is hopefully
>almost never.
>> Many of the uses of finalize() in the JDK today are bad and should be
>deleted.
>>
>> My fix, BTW, was to use a back door (that I added to SharedSecrets) in all
>the JDK classes that had a finalize() method, so that when a resource is
>properly closed, by calling the close() method for example, the back door
>would remove the Finalizer for the specified object from the linked list of
>Finalizer objects, thus removing it from the finalization equation altogether.
>I implemented this, and then the various tests of creating a huge number of
>objects with a finalize() method ran quickly and flawlessly with no horrific
>GCs or even a growing memory pool. The main problem with my solution was that
>there was this nasty SharedSecrets back door, so it has been rejected and
>probably rightly so.
>>
>> However, it proved a point.
>>
>> But now I am wondering why the actual right thing to do is not simply this:
>>
>> 	Remove the finalize() method from all the worst offenders in the JDK.
>>
>> I cannot remember all the places I patched when I implemented my fix, but
>the majority of them were pieces of code that absolutely had a close() method.
>If you don't close objects when you're done with them, your program PROBABLY
>SHOULD BE BROKEN. But even if you do not accept that, for all practical
>purposes, the program IS broken today because finalization is absolutely NOT
>run in a timely enough fashion.
>
>I have shown that we can have a cake and eat it too. Combining
>Finalizator-based clean-up with manual (or AutoCloseable) clean-up is a
>win-win situation. Programs that forget to call close() still work and
>those that do prompt close()ing will not be affected by finalization
>overhead. The migration of internal JDK code from finalize() methods to
>Finalizator-based cleanup should be simple and straight-forward.
>
>So what are we waiting for? ;-)
>
>> BTW - I never understood why CMS and other GC's had absolutely no problem
>running finalization in a very timely fashion while the G1 collector just
>never seemed to get around to it. My interpretation of that fact has always
>led me to believe that it's not a throughput issue with the finalization
>thread (not in real world examples, anyway) but rather a GC implementation
>that didn't feel the need to be thorough enough to make sure something is
>ready to be finalized. I mean, when the G1 collector was forced to run a full
>collection (a death sentence on a 15Gb heap but it did occur) all the
>finalizable objects were found AND finalized immediately, all 15 or 20 million
>of them.
>>
>> So in summary:
>>
>> (1) The problem with finalization is that people use it. And more
>importantly, that the JDK is filled with inappropriate uses of it.
>>
>> (2) The main solution is probably just to delete the inappropriate uses in
>the JDK. But if that's not OK, then some sort of patch like what I did which
>allows the JDK classes to unregister the Finalizer's when they are no longer
>needed, i.e., when the object knows that it has cleaned itself up.
>>
>> I am curious to hear your thoughts.
>
>Thanks for the description of the problem you have with finalization.
>JDK has an internal alternative to finalization called sun.misc.Cleaner,
>which has basically the same API and implementation as my presented
>Finalizator with the following differences:
>
>- Cleaner is a PhantomReference which means that the referent is not
>obtainable any more when it is triggered, so clean-up code can only work
>on state that is not part of the referent (captured by Cleaner's thunk
>at the time of construction). This is suitable sometimes but not always.
>- Cleaner(s) are executed by the ReferenceHandler thread direclty which
>makes them unsuitable for public consumption as their thunk's must
>guarantee to be executed quickly or else the whole reference processing
>infrastructure blocks. Finalizator(s) are executed by the same thread(s)
>as Finalizer(s).
>
>While it would be possible to retro-fit internal JDK classes to use
>Cleaner(s) instead of finalize() methods, this would require more
>refactoring which is always tricky. Finalizators, on the other hand,
>could be used as a drop-in replacement for finalize() method.
>
>> JP
>>
>
>Regards, Peter




More information about the hotspot-gc-dev mailing list