tutorial on using Cleaner-based finalization

Peter Levart peter.levart at gmail.com
Sat May 6 20:01:05 UTC 2017


Hi Rick and others,

On 05/04/2017 06:48 PM, Lance Andersen wrote:
> Here are a few examples I believe:
> http://svn.apache.org/repos/asf/db/derby/code/trunk/java/client/org/apache/derby/client/am/ClientConnection.java
> http://svn.apache.org/repos/asf/db/derby/code/trunk/java/client/org/apache/derby/client/ClientPooledConnection.java
> http://svn.apache.org/repos/asf/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedPreparedStatement.java

I took a bite at the 1st one (ClientConnection). Here's the result:

http://cr.openjdk.java.net/~plevart/misc/Cleaner/derby/ClientConnection_finalize2cleaner.patch

I haven't tested this, but I believe it should work. It was quite a 
challenge, because of the way current ClientConnection code is 
structured. I tried to make the patch not incompatibly change public API 
of ClientConnection and related classes and I almost succeeded. The 
problematic part was the protected boolean ClientConnection.closed_ 
flag. If there is any sublclass of ClientConnection (apart from 
NetConnection which is derby code) that modifies this field, you are out 
of luck as changing (not only reading) this field directly my have an 
undesirable consequence (or it may not, since the only thing that 
changing these field to false would do is it would redundantly force 
performing the cleanup action. If the cleanup is idempotent, then all is 
OK).

Further complication with ClientConnection is that it maintains a split 
state - some of it resides in ClientConnection and subclasses (such as 
NetConnection) and some of it in embedded object of Agent class and 
subclasses (such as NetAgent). Both - some of this state from connection 
object and some from the agent state are needed to perform the cleanup 
that is currently executed from the connection finalize() method. When 
using Cleaner API, we have to capture this state from both places (or 
more since each class has a hierarchy) and then arrange for cleanup 
action to use this state. Captured state can not reference the tracked 
object (ClientConnection in this case) either directly or indirectly 
since then it will never be GCed. When cleanup action is run, the 
tracked object is already unreachable - this is the main difference from 
finalize() where there is a phase in object's life-cycle where it is 
still reachable, albeit guaranteed only from the thread executing 
finalize() method. We can not capture the Agent object either, since it 
maintains a reference back to the ClientConnection object. All this is 
further complicated by the fact that captured state is mutable and we 
have to arrange for it to be mutated in both places. If the mutable 
state is captured by reference and the instance containing it never 
changes during the lifetime of the tracked container object, then it is 
easy - we just capture the object after the tracked container instance 
is constructed. If the captured state includes mutable fields directly 
in the tracked container object, then we must arrange for them to be 
synchronously mutated in two places. Such fields are:

- ClientConnection.open_ (replicated in ClientConnection.CleanupAction.open)
- Agent.logWriter_ (replicated in Agent.CleanupAction.logWriter)
- NetAgent.rawSocketInputStream_ (replicated in 
NetAgent.NetCleanupAction.rawSocketInputStream)
- NetAgent.rawSocketOutputStream_ (replicated in 
NetAgent.NetCleanupAction.rawSocketOutputStream)

Fortunately all of this state is encapsulated with protected field 
ClientConnection.open_ being an exception.

Note that Cleaner API also allows for cleanup action to be triggered 
explicitly, which then de-registers it. This is one of its advantages 
over finalize() where you can not deregister an object when it is 
already explicitly closed for example. finalize() will always be called 
even if closed explicitly. If you create lots of finalizable objects 
(such as connections, statements, etc...) and promptly close() them, 
they still wait for finalization and use resources (heap, CPU when GC 
searches for them, ReferenceHandler enqueues them, and finally 
finalize() method which is executed after the fact). Explicit triggering 
and de-registration of the cleanup action is performed in the 
ClientConnection.closeResourcesX() (called from public close() and 
closeResources()) after the connection has already being marked as 
closed. Cleanup action will be a no-op at this point, but it will also 
be de-registered. This is important to not bother GC with reference 
processing when it is not needed any more. In situations whre cleanup 
action logic is the same as explicit closing logic (in the case of 
ClientConnection it is not), close() method could just invoke 
cleanable.clean() and delegate the meat of processing to the cleanup action.

Hope this non trivial example helps illustrate what is needed when 
converting finalize() to Cleanup API.

Regards, Peter



More information about the core-libs-dev mailing list