RFR: JDK-8149925 We don't need jdk.internal.ref.Cleaner any more
peter.levart at gmail.com
Fri Apr 1 15:18:38 UTC 2016
Hi Mandy, Roger,
On 04/01/2016 06:07 AM, Mandy Chung wrote:
> Hi Peter,
> I found this thread has grown to discuss several relevant issues that can be separated.
> 1. Assist cleaner to deallocate direct byte buffer (JDK-6857566)
> 2. Replace direct byte buffer with java.lang.ref.Cleaner
> 3. java.lang.ref.Cleaner be an interface vs final class
> 4. Pending reference list lock (JDK-8055232)
> Roger has to speak for #3 which I don’t think he comments on that. For #4, working out the VM and library new interface to transfer the pending references definitely has to take more time and prototype. I’m interested in #4 but not sure if this can target in JDK 9 (given that FC in May).
> I’d like to address #1 to have the allocating thread to invoke cleaning actions to free native memory rather than any cleaning action. #2 is not as critical in my opinion while it’d be nice to get to. One possible option to move forward is to keep Cleaner as is and keep java.nio.Bits to invoke cleaning actions, i.e. webrev.08.part2 except that CleanerFactory will have two special cleaners - one for native memory cleaning and the other for anything else (there isn’t any client in JDK yet). We will see what Panama would provide for timely deallocation and we could replace the fix in Bits with that when’s available.
> My comments inlined below that are related #1 and #2.
>> On Mar 28, 2016, at 10:18 AM, Peter Levart <peter.levart at gmail.com> wrote:
>> But first, let me reply to Mandy's comments...
>>> My uncomfort was the fix for JDK-6857566 - both enqueuing pending ref and invoking the cleaning code in an arbitrary thread.
>>> Looking at it again - enqueuing the pending reference is not so much of a concern (simply updating the link) but the common cleaner could be used by other code that may only expect to be invoked in system thread that’s still my concern (thinking of thread locals).
>> As you'll see in the webrev below, enqueueing is performed solely be ReferenceHandler thread. Allocating thread(s) just wait for it to do its job. There's a little synchronization action performed at the end of enqueueing a chunk of pending references that notifies waiters (allocating threads) so that they can continue. This actually improves throughput (compared to helping enqueue Reference(s) one by one) because there's not much actual work to be done (just swapping pointers) so synchronization dominates. The goal here is to minimize synchronization among threads and by executing enqueuing of the whole bunch of pending references in private by a single thread achieves a reduction in synchronization when lots of Reference(s) are discovered at once - precisely the situation when it matters.
> I understand this and have no issue with this.
>> OTOH helping the Cleaner thread is beneficial as cleanup actions take time to execute and this is the easiest way to retry allocation while there's still chance it will succeed. As the common Cleaner is using InnocuousThread, cleanup actions can't rely on any thread locals to be preserved from invocation to invocation anyway - they are cleared after each cleanup action so each action gets empty thread locals. We could simulate this in threads that help execute cleanup actions by saving thread-locals to local variables, clearing thread-locals, executing cleanup action and then restoring thread-locals from local variables. Mandy, if you think this is important I'll add such save/clear/restore code to appropriate place.
> I’m comfortable of running unsafe::freeMemory in allocating thread. That’s why I propose to have a specific cleaner for native memory allocation use that seems to be the simplest approach (but if it turns out changing to Cleaner as as interface - it’s a different question). I can’t speak for NIO if we want to put save/clear/restore logic in java.nio.Bits.
Right, so let's focus on issue #1 for now. You would agree that to
separate cleaning actions for DirectByteBuffers from other cleaning
actions in the system, there has to be a special ReferenceQueue for
them, therefore a special Cleaner instance. If we keep
jdk.internal.ref.Cleaner (old sun.misc.Cleaner) just for
DirectByteBuffer then we get similar guarantee - helping
ReferenceHandler thread would just execute old Cleaners that deallocate
or unmap DirectByteBuffer(s) and no other(s). But that would also keep
helping threads enqueue other Reference(s) one by one and
ReferenceHandler would keep executing old Cleaner(s) which is
troublesome because of unresolved problems with OOME(s). It would also
be harder to resolve a subtle dilemma described below...
So I'm going to propose a solution for #1 while still keeping the rest
of webrev unchanged for now and will try to address other issuer later.
Here's new webrev:
...the only change from webrev.11.part2 is in CleanerFactory and
Direct-X-Buffer template. There are now two lazily initialized Cleaner
instances. A dbbCleaner() which is used only for DirectByteBuffer(s) and
has extended functionality and a common cleaner() which is used for all
other purposes in JDK and doesn't have the extended functionality.
The subtle dilemma here is whether to use the dbbCleaner() for all
DirectByteBuffer(s) or just for DirectByteBuffer(s) that are created by
allocating the native memory and not by mapping it. Only the former
DBB(s) use Bits.[un]reserveMemory to limit usage of direct memory. In
the webrev above I used dbbCleaner() for both types of DBB(s) so DBB
allocating threads also help unmap DBB(s). It is easy to separate those
two types and use dbbCleaner() for allocated DBB(s) and common cleaner()
for mapped DBB(s) because DirectByteBuffer has two distinct constructors
for the two usages and the two cleaners share a common Cleaner.Cleanable
type. Such easy separation would not be possible if you wanted to use
old jdk.internal.ref.Cleaner for allocated DBBs and the new
java.lang.ref.Cleaner for mapped DBBs.
I think that with above approach where helping threads are limited to
DirectByteBuffer.Deallocator(s) and FileChannelImpl.Unmapper(s) (or only
the former if desired) is quite safe. To guarantee (if supported by VM)
that helping thread either executes the whole cleaning operation or no
part of it, I added @ReservedStackAccess annotation on the critical
CleanerImpl.Task.cleanNextEnqueued() method which is called by helping
So, what do you think about this webrev from the issue #1 perspective?
I thought about how to instead of helping the background Cleaner thread,
make allocation threads just synchronize with cleaner thread but I
haven't yet come up with an elegant way that would not include lots of
additional synchronization which would represent overhead in normal
operation too. So if helping is limited to DBBs like suggested above, do
you still have any concerns?
About entanglement between nio Bits and
ExtendedCleaner.retryWhileHelpingClean(). It is the same level of
entanglement as between the DirectByteBuffer constructor and
Cleaner.register(). In both occasions an action is provided to the
Cleaner. Cleaner.register() takes a cleanup action and
ExtendedCleaner.retryWhileHelpingClean() takes a retriable "allocating"
or "reservation" action. "allocation" or "reservation" is the opposite
of cleanup. Both methods are encapsulated in the same object because
those two functions must be coordinated. So I think that collocating
them together makes sense. What do you think?
More information about the core-libs-dev