--- old/src/java.base/share/classes/java/nio/Bits.java 2016-03-24 16:33:10.485498730 +0100 +++ new/src/java.base/share/classes/java/nio/Bits.java 2016-03-24 16:33:10.395498258 +0100 @@ -25,13 +25,16 @@ package java.nio; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.StampedLock; import jdk.internal.misc.JavaNioAccess; import jdk.internal.misc.JavaLangRefAccess; import jdk.internal.misc.SharedSecrets; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; +import jdk.internal.ref.CleanerFactory; /** * Access to bits, native and otherwise. @@ -597,17 +600,57 @@ // A user-settable upper limit on the maximum amount of allocatable // direct buffer memory. This value may be changed during VM // initialization if it is launched with "-XX:MaxDirectMemorySize=". - private static volatile long maxMemory = VM.maxDirectMemory(); + // A change to maxMemory is published via memoryLimitSet volatile boolean + // flag changing state from false -> true, so maxMemory need not be volatile. + private static long maxMemory = VM.maxDirectMemory(); + private static volatile boolean memoryLimitSet; + private static final AtomicLong reservedMemory = new AtomicLong(); private static final AtomicLong totalCapacity = new AtomicLong(); private static final AtomicLong count = new AtomicLong(); - private static volatile boolean memoryLimitSet; - // max. number of sleeps during try-reserving with exponentially - // increasing delay before throwing OutOfMemoryError: - // 1, 2, 4, 8, 16, 32, 64, 128, 256 (total 511 ms ~ 0.5 s) - // which means that OOME will be thrown after 0.5 s of trying - private static final int MAX_SLEEPS = 9; + // A fair lock for direct memory allocator threads to queue after 1st optimistic + // reservation fails so that only a single thread at a time is retrying + // reservation while: + // - waiting for unreservation events until time out + // - followed by triggering reference discovery + // - followed by another round of waiting for unreservation events until time out + // ... before finally giving up with OutOfMemoryError. + private static final StampedLock reserveLock = new StampedLock(); + + // A counter of unreservations incremented by unreservation threads, + // guarded by unreserveLock, but also read by reservation threads without + // holding unreserveLock, so it must be volatile. + private static volatile int unreserveCount; + + // Last value of 'unreserveCount' observed by reservation threads, + // guarded by reserveLock. If 'lastUnreserveCount' and 'unreserveCount' + // differ, an unreservation event (or multiple events) is detected. + private static int lastUnreserveCount; + + // the System.nanoTime() of the detection of last unreservation event + // by the reservation thread(s), guarded by reserveLock. + private static long lastUnreserveNanoTime; + + // flag indicating if 'lastUnreserveNanoTime' has already been set, + // guarded by reserveLock. + private static boolean lastUnreserveNanoTimeSet; + + // max. wait time for unreservation event before triggering GC and/or + // failing with OOME. + private static final long MAX_UNRESERVE_WAIT_NANOS = + TimeUnit.SECONDS.toNanos(1L); + + // the duration of last successful waiting for unreservation + // event by reservation thread. starts with MAX_UNRESERVE_WAIT_NANOS / 4 + // and is dynamically adjusted at each successful call to awaitUnreserveMemory() + // that actually waited for the event. + private static long lastSuccessfullUnreserveWaitNanos = + MAX_UNRESERVE_WAIT_NANOS / 4; + + // monitor lock via which unreservation threads notify reservation threads + // about the unreservation events. + private static final Object unreserveLock = new Object(); // These methods should be called whenever direct memory is allocated or // freed. They allow the user to control the amount of direct memory @@ -619,57 +662,57 @@ memoryLimitSet = true; } - // optimist! - if (tryReserveMemory(size, cap)) { - return; - } - - final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess(); + JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess(); - // retry while helping enqueue pending Reference objects - // which includes executing pending Cleaner(s) which includes - // Cleaner(s) that free direct buffer memory - while (jlra.tryHandlePendingReference()) { - if (tryReserveMemory(size, cap)) { - return; - } + // optimist! + long stamp = reserveLock.tryReadLock(); + if (stamp != 0L) try { + // retry reservation while helping the Cleaner thread process enqueued + // Cleanable(s) which includes the ones that free direct buffer memory + // until the queue drains out + do { + if (tryReserveMemory(size, cap)) { + return; + } + } while (jlra.cleanNextEnqueuedCleanable(CleanerFactory.cleaner())); + } finally { + reserveLock.unlockRead(stamp); } - // trigger VM's Reference processing - System.gc(); - - // a retry loop with exponential back-off delays - // (this gives VM some time to do it's job) - boolean interrupted = false; + // reservation threads that don't succeed at first must queue so that + // only one of them at a time is triggering reference discovery and + // retrials with waiting on un-reservation events... + stamp = reserveLock.writeLock(); try { - long sleepTime = 1; - int sleeps = 0; - while (true) { + // retry reservation until there are no unreservation events + // detected for at least 4 times as much as we waited for last + // successful unreservation event but no more than + // MAX_UNRESERVE_WAIT_NANOS. + do { if (tryReserveMemory(size, cap)) { return; } - if (sleeps >= MAX_SLEEPS) { - break; - } - if (!jlra.tryHandlePendingReference()) { - try { - Thread.sleep(sleepTime); - sleepTime <<= 1; - sleeps++; - } catch (InterruptedException e) { - interrupted = true; - } + } while (awaitUnreserveMemory(Math.min(MAX_UNRESERVE_WAIT_NANOS, + lastSuccessfullUnreserveWaitNanos * 4))); + + // trigger reference discovery + System.gc(); + // GC stops the world and can last for a long time, + // so restart timing from 0. + lastUnreserveNanoTime = System.nanoTime(); + + // retry reservation until there are no unreservation events + // detected for at least MAX_UNRESERVE_WAIT_NANOS. + do { + if (tryReserveMemory(size, cap)) { + return; } - } + } while (awaitUnreserveMemory(MAX_UNRESERVE_WAIT_NANOS)); // no luck throw new OutOfMemoryError("Direct buffer memory"); - } finally { - if (interrupted) { - // don't swallow interrupts - Thread.currentThread().interrupt(); - } + reserveLock.unlockWrite(stamp); } } @@ -690,12 +733,69 @@ return false; } + private static boolean awaitUnreserveMemory(long timeoutNanos) { + assert reserveLock.isWriteLocked(); + + // optimistic 1st try without lock + int c; + if ((c = unreserveCount) != lastUnreserveCount) { + lastUnreserveCount = c; + lastUnreserveNanoTime = System.nanoTime(); + lastUnreserveNanoTimeSet = true; + return true; + } + boolean interrupted = false; + try { + synchronized (unreserveLock) { + long nt = System.nanoTime(); + if (!lastUnreserveNanoTimeSet) { + lastUnreserveNanoTime = nt; + lastUnreserveNanoTimeSet = true; + } + long deadline = lastUnreserveNanoTime + timeoutNanos; + long waitUnreserve; + while ((c = unreserveCount) == lastUnreserveCount && + (waitUnreserve = deadline - nt) > 0L) { + interrupted |= waitNanos(unreserveLock, waitUnreserve); + nt = System.nanoTime(); + } + if (c == lastUnreserveCount) { + // time out + return false; + } else { + lastSuccessfullUnreserveWaitNanos = nt - lastUnreserveNanoTime; + lastUnreserveCount = c; + lastUnreserveNanoTime = nt; + return true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + private static boolean waitNanos(Object lock, long waitNanos) { + try { + long millis = TimeUnit.NANOSECONDS.toMillis(waitNanos); + int nanos = (int) (waitNanos - TimeUnit.MILLISECONDS.toNanos(millis)); + lock.wait(millis, nanos); + return false; + } catch (InterruptedException e) { + return true; + } + } static void unreserveMemory(long size, int cap) { - long cnt = count.decrementAndGet(); - long reservedMem = reservedMemory.addAndGet(-size); - long totalCap = totalCapacity.addAndGet(-cap); - assert cnt >= 0 && reservedMem >= 0 && totalCap >= 0; + synchronized (unreserveLock) { + long cnt = count.decrementAndGet(); + long reservedMem = reservedMemory.addAndGet(-size); + long totalCap = totalCapacity.addAndGet(-cap); + assert cnt >= 0 && reservedMem >= 0 && totalCap >= 0; + unreserveCount++; + unreserveLock.notifyAll(); + } } // -- Monitoring of direct buffer usage --