< prev index next >
src/java.base/share/classes/java/nio/Bits.java
Print this page
@@ -23,17 +23,20 @@
* questions.
*/
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.
*/
@@ -595,21 +598,61 @@
// -- Direct memory management --
// 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=<size>".
- 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
// which a process may access. All sizes are specified in bytes.
static void reserveMemory(long size, int cap) {
@@ -617,61 +660,61 @@
if (!memoryLimitSet && VM.initLevel() >= 1) {
maxMemory = VM.maxDirectMemory();
memoryLimitSet = true;
}
+ JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();
+
// 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);
+ }
- final 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()) {
+ // 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 {
+ // 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;
}
- }
+ } while (awaitUnreserveMemory(Math.min(MAX_UNRESERVE_WAIT_NANOS,
+ lastSuccessfullUnreserveWaitNanos * 4)));
- // trigger VM's Reference processing
+ // trigger reference discovery
System.gc();
-
- // a retry loop with exponential back-off delays
- // (this gives VM some time to do it's job)
- boolean interrupted = false;
- try {
- long sleepTime = 1;
- int sleeps = 0;
- while (true) {
+ // 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;
}
- if (sleeps >= MAX_SLEEPS) {
- break;
- }
- if (!jlra.tryHandlePendingReference()) {
- try {
- Thread.sleep(sleepTime);
- sleepTime <<= 1;
- sleeps++;
- } catch (InterruptedException e) {
- interrupted = true;
- }
- }
- }
+ } 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);
}
}
private static boolean tryReserveMemory(long size, int cap) {
@@ -688,16 +731,73 @@
}
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) {
+ 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 --
static {
< prev index next >