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