< 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 >