mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 07:14:30 +02:00
8066859: java/lang/ref/OOMEInReferenceHandler.java failed with java.lang.Exception: Reference Handler thread died
Reviewed-by: alanb
This commit is contained in:
parent
2583feb21b
commit
535804554d
5 changed files with 351 additions and 61 deletions
|
@ -195,31 +195,53 @@ public abstract class AbstractQueuedLongSynchronizer
|
||||||
return U.compareAndSetReference(this, TAIL, c, v);
|
return U.compareAndSetReference(this, TAIL, c, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** tries once to CAS a new dummy node for head */
|
/**
|
||||||
private void tryInitializeHead() {
|
* Tries to CAS a new dummy node for head.
|
||||||
Node h = new ExclusiveNode();
|
* Returns new tail, or null if OutOfMemory
|
||||||
if (U.compareAndSetReference(this, HEAD, null, h))
|
*/
|
||||||
tail = h;
|
private Node tryInitializeHead() {
|
||||||
|
for (Node h = null, t;;) {
|
||||||
|
if ((t = tail) != null)
|
||||||
|
return t;
|
||||||
|
else if (head != null)
|
||||||
|
Thread.onSpinWait();
|
||||||
|
else {
|
||||||
|
if (h == null) {
|
||||||
|
try {
|
||||||
|
h = new ExclusiveNode();
|
||||||
|
} catch (OutOfMemoryError oome) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (U.compareAndSetReference(this, HEAD, null, h))
|
||||||
|
return tail = h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enqueues the node unless null. (Currently used only for
|
* Enqueues the node unless null. (Currently used only for
|
||||||
* ConditionNodes; other cases are interleaved with acquires.)
|
* ConditionNodes; other cases are interleaved with acquires.)
|
||||||
*/
|
*/
|
||||||
final void enqueue(Node node) {
|
final void enqueue(ConditionNode node) {
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
for (;;) {
|
boolean unpark = false;
|
||||||
Node t = tail;
|
for (Node t;;) {
|
||||||
|
if ((t = tail) == null && (t = tryInitializeHead()) == null) {
|
||||||
|
unpark = true; // wake up to spin on OOME
|
||||||
|
break;
|
||||||
|
}
|
||||||
node.setPrevRelaxed(t); // avoid unnecessary fence
|
node.setPrevRelaxed(t); // avoid unnecessary fence
|
||||||
if (t == null) // initialize
|
if (casTail(t, node)) {
|
||||||
tryInitializeHead();
|
|
||||||
else if (casTail(t, node)) {
|
|
||||||
t.next = node;
|
t.next = node;
|
||||||
if (t.status < 0) // wake up to clean link
|
if (t.status < 0) // wake up to clean link
|
||||||
LockSupport.unpark(node.waiter);
|
unpark = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (unpark)
|
||||||
|
LockSupport.unpark(node.waiter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +300,10 @@ public abstract class AbstractQueuedLongSynchronizer
|
||||||
* Check if node now first
|
* Check if node now first
|
||||||
* if so, ensure head stable, else ensure valid predecessor
|
* if so, ensure head stable, else ensure valid predecessor
|
||||||
* if node is first or not yet enqueued, try acquiring
|
* if node is first or not yet enqueued, try acquiring
|
||||||
|
* else if queue is not initialized, do so by attaching new header node
|
||||||
|
* resort to spinwait on OOME trying to create node
|
||||||
* else if node not yet created, create it
|
* else if node not yet created, create it
|
||||||
|
* resort to spinwait on OOME trying to create node
|
||||||
* else if not yet enqueued, try once to enqueue
|
* else if not yet enqueued, try once to enqueue
|
||||||
* else if woken from park, retry (up to postSpins times)
|
* else if woken from park, retry (up to postSpins times)
|
||||||
* else if WAITING status not set, set and retry
|
* else if WAITING status not set, set and retry
|
||||||
|
@ -321,18 +346,20 @@ public abstract class AbstractQueuedLongSynchronizer
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node == null) { // allocate; retry before enqueue
|
Node t;
|
||||||
if (shared)
|
if ((t = tail) == null) { // initialize queue
|
||||||
node = new SharedNode();
|
if (tryInitializeHead() == null)
|
||||||
else
|
return acquireOnOOME(shared, arg);
|
||||||
node = new ExclusiveNode();
|
} else if (node == null) { // allocate; retry before enqueue
|
||||||
|
try {
|
||||||
|
node = (shared) ? new SharedNode() : new ExclusiveNode();
|
||||||
|
} catch (OutOfMemoryError oome) {
|
||||||
|
return acquireOnOOME(shared, arg);
|
||||||
|
}
|
||||||
} else if (pred == null) { // try to enqueue
|
} else if (pred == null) { // try to enqueue
|
||||||
node.waiter = current;
|
node.waiter = current;
|
||||||
Node t = tail;
|
|
||||||
node.setPrevRelaxed(t); // avoid unnecessary fence
|
node.setPrevRelaxed(t); // avoid unnecessary fence
|
||||||
if (t == null)
|
if (!casTail(t, node))
|
||||||
tryInitializeHead();
|
|
||||||
else if (!casTail(t, node))
|
|
||||||
node.setPrevRelaxed(null); // back out
|
node.setPrevRelaxed(null); // back out
|
||||||
else
|
else
|
||||||
t.next = node;
|
t.next = node;
|
||||||
|
@ -358,9 +385,23 @@ public abstract class AbstractQueuedLongSynchronizer
|
||||||
return cancelAcquire(node, interrupted, interruptible);
|
return cancelAcquire(node, interrupted, interruptible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spin-waits with backoff; used only upon OOME failures during acquire.
|
||||||
|
*/
|
||||||
|
private int acquireOnOOME(boolean shared, long arg) {
|
||||||
|
for (long nanos = 1L;;) {
|
||||||
|
if (shared ? (tryAcquireShared(arg) >= 0) : tryAcquire(arg))
|
||||||
|
return 1;
|
||||||
|
U.park(false, nanos); // must use Unsafe park to sleep
|
||||||
|
if (nanos < 1L << 30) // max about 1 second
|
||||||
|
nanos <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Possibly repeatedly traverses from tail, unsplicing cancelled
|
* Possibly repeatedly traverses from tail, unsplicing cancelled
|
||||||
* nodes until none are found.
|
* nodes until none are found. Unparks nodes that may have been
|
||||||
|
* relinked to be next eligible acquirer.
|
||||||
*/
|
*/
|
||||||
private void cleanQueue() {
|
private void cleanQueue() {
|
||||||
for (;;) { // restart point
|
for (;;) { // restart point
|
||||||
|
@ -1067,6 +1108,12 @@ public abstract class AbstractQueuedLongSynchronizer
|
||||||
/** Last node of condition queue. */
|
/** Last node of condition queue. */
|
||||||
private transient ConditionNode lastWaiter;
|
private transient ConditionNode lastWaiter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixed delay in nanoseconds between releasing and reacquiring
|
||||||
|
* lock during Condition waits that encounter OutOfMemoryErrors
|
||||||
|
*/
|
||||||
|
static final long OOME_COND_WAIT_DELAY = 10L * 1000L * 1000L; // 10 ms
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@code ConditionObject} instance.
|
* Creates a new {@code ConditionObject} instance.
|
||||||
*/
|
*/
|
||||||
|
@ -1103,7 +1150,7 @@ public abstract class AbstractQueuedLongSynchronizer
|
||||||
ConditionNode first = firstWaiter;
|
ConditionNode first = firstWaiter;
|
||||||
if (!isHeldExclusively())
|
if (!isHeldExclusively())
|
||||||
throw new IllegalMonitorStateException();
|
throw new IllegalMonitorStateException();
|
||||||
if (first != null)
|
else if (first != null)
|
||||||
doSignal(first, false);
|
doSignal(first, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1118,7 +1165,7 @@ public abstract class AbstractQueuedLongSynchronizer
|
||||||
ConditionNode first = firstWaiter;
|
ConditionNode first = firstWaiter;
|
||||||
if (!isHeldExclusively())
|
if (!isHeldExclusively())
|
||||||
throw new IllegalMonitorStateException();
|
throw new IllegalMonitorStateException();
|
||||||
if (first != null)
|
else if (first != null)
|
||||||
doSignal(first, true);
|
doSignal(first, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1185,6 +1232,26 @@ public abstract class AbstractQueuedLongSynchronizer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs objects needed for condition wait. On OOME,
|
||||||
|
* releases lock, sleeps, reacquires, and returns null.
|
||||||
|
*/
|
||||||
|
private ConditionNode newConditionNode() {
|
||||||
|
long savedState;
|
||||||
|
if (tryInitializeHead() != null) {
|
||||||
|
try {
|
||||||
|
return new ConditionNode();
|
||||||
|
} catch (OutOfMemoryError oome) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fall through if encountered OutOfMemoryError
|
||||||
|
if (!isHeldExclusively() || !release(savedState = getState()))
|
||||||
|
throw new IllegalMonitorStateException();
|
||||||
|
U.park(false, OOME_COND_WAIT_DELAY);
|
||||||
|
acquireOnOOME(false, savedState);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements uninterruptible condition wait.
|
* Implements uninterruptible condition wait.
|
||||||
* <ol>
|
* <ol>
|
||||||
|
@ -1198,6 +1265,8 @@ public abstract class AbstractQueuedLongSynchronizer
|
||||||
*/
|
*/
|
||||||
public final void awaitUninterruptibly() {
|
public final void awaitUninterruptibly() {
|
||||||
ConditionNode node = newConditionNode();
|
ConditionNode node = newConditionNode();
|
||||||
|
if (node == null)
|
||||||
|
return;
|
||||||
long savedState = enableWait(node);
|
long savedState = enableWait(node);
|
||||||
LockSupport.setCurrentBlocker(this); // for back-compatibility
|
LockSupport.setCurrentBlocker(this); // for back-compatibility
|
||||||
boolean interrupted = false, rejected = false;
|
boolean interrupted = false, rejected = false;
|
||||||
|
@ -1242,6 +1311,8 @@ public abstract class AbstractQueuedLongSynchronizer
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
ConditionNode node = newConditionNode();
|
ConditionNode node = newConditionNode();
|
||||||
|
if (node == null)
|
||||||
|
return;
|
||||||
long savedState = enableWait(node);
|
long savedState = enableWait(node);
|
||||||
LockSupport.setCurrentBlocker(this); // for back-compatibility
|
LockSupport.setCurrentBlocker(this); // for back-compatibility
|
||||||
boolean interrupted = false, cancelled = false, rejected = false;
|
boolean interrupted = false, cancelled = false, rejected = false;
|
||||||
|
@ -1293,6 +1364,8 @@ public abstract class AbstractQueuedLongSynchronizer
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
ConditionNode node = newConditionNode();
|
ConditionNode node = newConditionNode();
|
||||||
|
if (node == null)
|
||||||
|
return nanosTimeout - OOME_COND_WAIT_DELAY;
|
||||||
long savedState = enableWait(node);
|
long savedState = enableWait(node);
|
||||||
long nanos = (nanosTimeout < 0L) ? 0L : nanosTimeout;
|
long nanos = (nanosTimeout < 0L) ? 0L : nanosTimeout;
|
||||||
long deadline = System.nanoTime() + nanos;
|
long deadline = System.nanoTime() + nanos;
|
||||||
|
@ -1337,6 +1410,8 @@ public abstract class AbstractQueuedLongSynchronizer
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
ConditionNode node = newConditionNode();
|
ConditionNode node = newConditionNode();
|
||||||
|
if (node == null)
|
||||||
|
return false;
|
||||||
long savedState = enableWait(node);
|
long savedState = enableWait(node);
|
||||||
boolean cancelled = false, interrupted = false;
|
boolean cancelled = false, interrupted = false;
|
||||||
while (!canReacquire(node)) {
|
while (!canReacquire(node)) {
|
||||||
|
@ -1378,6 +1453,8 @@ public abstract class AbstractQueuedLongSynchronizer
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
ConditionNode node = newConditionNode();
|
ConditionNode node = newConditionNode();
|
||||||
|
if (node == null)
|
||||||
|
return false;
|
||||||
long savedState = enableWait(node);
|
long savedState = enableWait(node);
|
||||||
long nanos = (nanosTimeout < 0L) ? 0L : nanosTimeout;
|
long nanos = (nanosTimeout < 0L) ? 0L : nanosTimeout;
|
||||||
long deadline = System.nanoTime() + nanos;
|
long deadline = System.nanoTime() + nanos;
|
||||||
|
|
|
@ -432,6 +432,19 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
* methods. (It is usually easy for compilers to optimize
|
* methods. (It is usually easy for compilers to optimize
|
||||||
* call-site specializations when heavily used.)
|
* call-site specializations when heavily used.)
|
||||||
*
|
*
|
||||||
|
* Most AQS methods may be called by JDK components that cannot be
|
||||||
|
* allowed to fail when encountering OutOfMemoryErrors. The main
|
||||||
|
* acquire method resorts to spin-waits with backoff if nodes
|
||||||
|
* cannot be allocated. Condition waits release and reacquire
|
||||||
|
* locks upon OOME at a slow fixed rate (OOME_COND_WAIT_DELAY)
|
||||||
|
* designed with the hope that eventually enough memory will be
|
||||||
|
* recovered; if not performance can be very slow. Effectiveness
|
||||||
|
* is also limited by the possibility of class loading triggered
|
||||||
|
* by first-time usages, that may encounter unrecoverable
|
||||||
|
* OOMEs. Also, it is possible for OutOfMemoryErrors to be thrown
|
||||||
|
* when attempting to create and throw
|
||||||
|
* IllegalMonitorStateExceptions and InterruptedExceptions.
|
||||||
|
*
|
||||||
* There are several arbitrary decisions about when and how to
|
* There are several arbitrary decisions about when and how to
|
||||||
* check interrupts in both acquire and await before and/or after
|
* check interrupts in both acquire and await before and/or after
|
||||||
* blocking. The decisions are less arbitrary in implementation
|
* blocking. The decisions are less arbitrary in implementation
|
||||||
|
@ -562,31 +575,52 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
return U.compareAndSetReference(this, TAIL, c, v);
|
return U.compareAndSetReference(this, TAIL, c, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** tries once to CAS a new dummy node for head */
|
/**
|
||||||
private void tryInitializeHead() {
|
* Tries to CAS a new dummy node for head.
|
||||||
Node h = new ExclusiveNode();
|
* Returns new tail, or null if OutOfMemory
|
||||||
|
*/
|
||||||
|
private Node tryInitializeHead() {
|
||||||
|
for (Node h = null, t;;) {
|
||||||
|
if ((t = tail) != null)
|
||||||
|
return t;
|
||||||
|
else if (head != null)
|
||||||
|
Thread.onSpinWait();
|
||||||
|
else {
|
||||||
|
if (h == null) {
|
||||||
|
try {
|
||||||
|
h = new ExclusiveNode();
|
||||||
|
} catch (OutOfMemoryError oome) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (U.compareAndSetReference(this, HEAD, null, h))
|
if (U.compareAndSetReference(this, HEAD, null, h))
|
||||||
tail = h;
|
return tail = h;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enqueues the node unless null. (Currently used only for
|
* Enqueues the node unless null. (Currently used only for
|
||||||
* ConditionNodes; other cases are interleaved with acquires.)
|
* ConditionNodes; other cases are interleaved with acquires.)
|
||||||
*/
|
*/
|
||||||
final void enqueue(Node node) {
|
final void enqueue(ConditionNode node) {
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
for (;;) {
|
boolean unpark = false;
|
||||||
Node t = tail;
|
for (Node t;;) {
|
||||||
|
if ((t = tail) == null && (t = tryInitializeHead()) == null) {
|
||||||
|
unpark = true; // wake up to spin on OOME
|
||||||
|
break;
|
||||||
|
}
|
||||||
node.setPrevRelaxed(t); // avoid unnecessary fence
|
node.setPrevRelaxed(t); // avoid unnecessary fence
|
||||||
if (t == null) // initialize
|
if (casTail(t, node)) {
|
||||||
tryInitializeHead();
|
|
||||||
else if (casTail(t, node)) {
|
|
||||||
t.next = node;
|
t.next = node;
|
||||||
if (t.status < 0) // wake up to clean link
|
if (t.status < 0) // wake up to clean link
|
||||||
LockSupport.unpark(node.waiter);
|
unpark = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (unpark)
|
||||||
|
LockSupport.unpark(node.waiter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -645,7 +679,10 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
* Check if node now first
|
* Check if node now first
|
||||||
* if so, ensure head stable, else ensure valid predecessor
|
* if so, ensure head stable, else ensure valid predecessor
|
||||||
* if node is first or not yet enqueued, try acquiring
|
* if node is first or not yet enqueued, try acquiring
|
||||||
|
* else if queue is not initialized, do so by attaching new header node
|
||||||
|
* resort to spinwait on OOME trying to create node
|
||||||
* else if node not yet created, create it
|
* else if node not yet created, create it
|
||||||
|
* resort to spinwait on OOME trying to create node
|
||||||
* else if not yet enqueued, try once to enqueue
|
* else if not yet enqueued, try once to enqueue
|
||||||
* else if woken from park, retry (up to postSpins times)
|
* else if woken from park, retry (up to postSpins times)
|
||||||
* else if WAITING status not set, set and retry
|
* else if WAITING status not set, set and retry
|
||||||
|
@ -688,18 +725,20 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node == null) { // allocate; retry before enqueue
|
Node t;
|
||||||
if (shared)
|
if ((t = tail) == null) { // initialize queue
|
||||||
node = new SharedNode();
|
if (tryInitializeHead() == null)
|
||||||
else
|
return acquireOnOOME(shared, arg);
|
||||||
node = new ExclusiveNode();
|
} else if (node == null) { // allocate; retry before enqueue
|
||||||
|
try {
|
||||||
|
node = (shared) ? new SharedNode() : new ExclusiveNode();
|
||||||
|
} catch (OutOfMemoryError oome) {
|
||||||
|
return acquireOnOOME(shared, arg);
|
||||||
|
}
|
||||||
} else if (pred == null) { // try to enqueue
|
} else if (pred == null) { // try to enqueue
|
||||||
node.waiter = current;
|
node.waiter = current;
|
||||||
Node t = tail;
|
|
||||||
node.setPrevRelaxed(t); // avoid unnecessary fence
|
node.setPrevRelaxed(t); // avoid unnecessary fence
|
||||||
if (t == null)
|
if (!casTail(t, node))
|
||||||
tryInitializeHead();
|
|
||||||
else if (!casTail(t, node))
|
|
||||||
node.setPrevRelaxed(null); // back out
|
node.setPrevRelaxed(null); // back out
|
||||||
else
|
else
|
||||||
t.next = node;
|
t.next = node;
|
||||||
|
@ -725,6 +764,19 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
return cancelAcquire(node, interrupted, interruptible);
|
return cancelAcquire(node, interrupted, interruptible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spin-waits with backoff; used only upon OOME failures during acquire.
|
||||||
|
*/
|
||||||
|
private int acquireOnOOME(boolean shared, int arg) {
|
||||||
|
for (long nanos = 1L;;) {
|
||||||
|
if (shared ? (tryAcquireShared(arg) >= 0) : tryAcquire(arg))
|
||||||
|
return 1;
|
||||||
|
U.park(false, nanos); // must use Unsafe park to sleep
|
||||||
|
if (nanos < 1L << 30) // max about 1 second
|
||||||
|
nanos <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Possibly repeatedly traverses from tail, unsplicing cancelled
|
* Possibly repeatedly traverses from tail, unsplicing cancelled
|
||||||
* nodes until none are found. Unparks nodes that may have been
|
* nodes until none are found. Unparks nodes that may have been
|
||||||
|
@ -1435,6 +1487,12 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
/** Last node of condition queue. */
|
/** Last node of condition queue. */
|
||||||
private transient ConditionNode lastWaiter;
|
private transient ConditionNode lastWaiter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixed delay in nanoseconds between releasing and reacquiring
|
||||||
|
* lock during Condition waits that encounter OutOfMemoryErrors
|
||||||
|
*/
|
||||||
|
static final long OOME_COND_WAIT_DELAY = 10L * 1000L * 1000L; // 10 ms
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@code ConditionObject} instance.
|
* Creates a new {@code ConditionObject} instance.
|
||||||
*/
|
*/
|
||||||
|
@ -1471,7 +1529,7 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
ConditionNode first = firstWaiter;
|
ConditionNode first = firstWaiter;
|
||||||
if (!isHeldExclusively())
|
if (!isHeldExclusively())
|
||||||
throw new IllegalMonitorStateException();
|
throw new IllegalMonitorStateException();
|
||||||
if (first != null)
|
else if (first != null)
|
||||||
doSignal(first, false);
|
doSignal(first, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1486,7 +1544,7 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
ConditionNode first = firstWaiter;
|
ConditionNode first = firstWaiter;
|
||||||
if (!isHeldExclusively())
|
if (!isHeldExclusively())
|
||||||
throw new IllegalMonitorStateException();
|
throw new IllegalMonitorStateException();
|
||||||
if (first != null)
|
else if (first != null)
|
||||||
doSignal(first, true);
|
doSignal(first, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1553,6 +1611,26 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs objects needed for condition wait. On OOME,
|
||||||
|
* releases lock, sleeps, reacquires, and returns null.
|
||||||
|
*/
|
||||||
|
private ConditionNode newConditionNode() {
|
||||||
|
int savedState;
|
||||||
|
if (tryInitializeHead() != null) {
|
||||||
|
try {
|
||||||
|
return new ConditionNode();
|
||||||
|
} catch (OutOfMemoryError oome) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fall through if encountered OutOfMemoryError
|
||||||
|
if (!isHeldExclusively() || !release(savedState = getState()))
|
||||||
|
throw new IllegalMonitorStateException();
|
||||||
|
U.park(false, OOME_COND_WAIT_DELAY);
|
||||||
|
acquireOnOOME(false, savedState);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements uninterruptible condition wait.
|
* Implements uninterruptible condition wait.
|
||||||
* <ol>
|
* <ol>
|
||||||
|
@ -1566,6 +1644,8 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
*/
|
*/
|
||||||
public final void awaitUninterruptibly() {
|
public final void awaitUninterruptibly() {
|
||||||
ConditionNode node = newConditionNode();
|
ConditionNode node = newConditionNode();
|
||||||
|
if (node == null)
|
||||||
|
return;
|
||||||
int savedState = enableWait(node);
|
int savedState = enableWait(node);
|
||||||
LockSupport.setCurrentBlocker(this); // for back-compatibility
|
LockSupport.setCurrentBlocker(this); // for back-compatibility
|
||||||
boolean interrupted = false, rejected = false;
|
boolean interrupted = false, rejected = false;
|
||||||
|
@ -1610,6 +1690,8 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
ConditionNode node = newConditionNode();
|
ConditionNode node = newConditionNode();
|
||||||
|
if (node == null)
|
||||||
|
return;
|
||||||
int savedState = enableWait(node);
|
int savedState = enableWait(node);
|
||||||
LockSupport.setCurrentBlocker(this); // for back-compatibility
|
LockSupport.setCurrentBlocker(this); // for back-compatibility
|
||||||
boolean interrupted = false, cancelled = false, rejected = false;
|
boolean interrupted = false, cancelled = false, rejected = false;
|
||||||
|
@ -1661,6 +1743,8 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
ConditionNode node = newConditionNode();
|
ConditionNode node = newConditionNode();
|
||||||
|
if (node == null)
|
||||||
|
return nanosTimeout - OOME_COND_WAIT_DELAY;
|
||||||
int savedState = enableWait(node);
|
int savedState = enableWait(node);
|
||||||
long nanos = (nanosTimeout < 0L) ? 0L : nanosTimeout;
|
long nanos = (nanosTimeout < 0L) ? 0L : nanosTimeout;
|
||||||
long deadline = System.nanoTime() + nanos;
|
long deadline = System.nanoTime() + nanos;
|
||||||
|
@ -1705,6 +1789,8 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
ConditionNode node = newConditionNode();
|
ConditionNode node = newConditionNode();
|
||||||
|
if (node == null)
|
||||||
|
return false;
|
||||||
int savedState = enableWait(node);
|
int savedState = enableWait(node);
|
||||||
boolean cancelled = false, interrupted = false;
|
boolean cancelled = false, interrupted = false;
|
||||||
while (!canReacquire(node)) {
|
while (!canReacquire(node)) {
|
||||||
|
@ -1746,6 +1832,8 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
if (Thread.interrupted())
|
if (Thread.interrupted())
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
ConditionNode node = newConditionNode();
|
ConditionNode node = newConditionNode();
|
||||||
|
if (node == null)
|
||||||
|
return false;
|
||||||
int savedState = enableWait(node);
|
int savedState = enableWait(node);
|
||||||
long nanos = (nanosTimeout < 0L) ? 0L : nanosTimeout;
|
long nanos = (nanosTimeout < 0L) ? 0L : nanosTimeout;
|
||||||
long deadline = System.nanoTime() + nanos;
|
long deadline = System.nanoTime() + nanos;
|
||||||
|
@ -1851,7 +1939,6 @@ public abstract class AbstractQueuedSynchronizer
|
||||||
= U.objectFieldOffset(AbstractQueuedSynchronizer.class, "head");
|
= U.objectFieldOffset(AbstractQueuedSynchronizer.class, "head");
|
||||||
private static final long TAIL
|
private static final long TAIL
|
||||||
= U.objectFieldOffset(AbstractQueuedSynchronizer.class, "tail");
|
= U.objectFieldOffset(AbstractQueuedSynchronizer.class, "tail");
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Class<?> ensureLoaded = LockSupport.class;
|
Class<?> ensureLoaded = LockSupport.class;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,5 +29,4 @@
|
||||||
|
|
||||||
java/lang/invoke/MethodHandles/CatchExceptionTest.java 8146623 generic-all
|
java/lang/invoke/MethodHandles/CatchExceptionTest.java 8146623 generic-all
|
||||||
java/lang/ref/ReferenceEnqueue.java 8284236 generic-all
|
java/lang/ref/ReferenceEnqueue.java 8284236 generic-all
|
||||||
java/lang/ref/OOMEInReferenceHandler.java 8066859 generic-all
|
|
||||||
java/lang/reflect/callerCache/ReflectionCallerCacheTest.java 8288286 generic-all
|
java/lang/reflect/callerCache/ReflectionCallerCacheTest.java 8288286 generic-all
|
||||||
|
|
|
@ -495,7 +495,6 @@ java/lang/invoke/LFCaching/LFMultiThreadCachingTest.java 8151492 generic-
|
||||||
java/lang/invoke/LFCaching/LFGarbageCollectedTest.java 8078602 generic-all
|
java/lang/invoke/LFCaching/LFGarbageCollectedTest.java 8078602 generic-all
|
||||||
java/lang/invoke/lambda/LambdaFileEncodingSerialization.java 8249079 linux-x64
|
java/lang/invoke/lambda/LambdaFileEncodingSerialization.java 8249079 linux-x64
|
||||||
java/lang/invoke/RicochetTest.java 8251969 generic-all
|
java/lang/invoke/RicochetTest.java 8251969 generic-all
|
||||||
java/lang/ref/OOMEInReferenceHandler.java 8066859 generic-all
|
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
|
|
||||||
|
|
128
test/jdk/java/util/concurrent/locks/Lock/OOMEInAQS.java
Normal file
128
test/jdk/java/util/concurrent/locks/Lock/OOMEInAQS.java
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.Phaser;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8066859
|
||||||
|
* @summary Check that AQS-based locks, conditions, and CountDownLatches do not fail when encountering OOME
|
||||||
|
* @run main/othervm -XX:-UseGCOverheadLimit -Xmx24M -XX:-UseTLAB OOMEInAQS
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class OOMEInAQS extends Thread {
|
||||||
|
static final int NTHREADS = 2; // intentionally not a scalable test; > 2 is very slow
|
||||||
|
static final int NREPS = 100;
|
||||||
|
// statically allocate
|
||||||
|
static final ReentrantLock mainLock = new ReentrantLock();
|
||||||
|
static final Condition condition = mainLock.newCondition();
|
||||||
|
static final CountDownLatch started = new CountDownLatch(1);
|
||||||
|
static final CountDownLatch filled = new CountDownLatch(1);
|
||||||
|
static volatile Object data;
|
||||||
|
static int turn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each of NTHREADS threads, REPS times: Take turns
|
||||||
|
* executing. Introduce OOM using fillHeap during runs.
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) throws Throwable {
|
||||||
|
OOMEInAQS[] threads = new OOMEInAQS[NTHREADS];
|
||||||
|
for (int i = 0; i < NTHREADS; ++i)
|
||||||
|
(threads[i] = new OOMEInAQS(i)).start();
|
||||||
|
started.countDown();
|
||||||
|
long t0 = System.nanoTime();
|
||||||
|
data = fillHeap();
|
||||||
|
filled.countDown();
|
||||||
|
long t1 = System.nanoTime();
|
||||||
|
for (int i = 0; i < NTHREADS; ++i)
|
||||||
|
threads[i].join();
|
||||||
|
data = null; // free heap before reporting and terminating
|
||||||
|
System.gc();
|
||||||
|
System.out.println(
|
||||||
|
"fillHeap time: " + (t1 - t0) / 1000_000 +
|
||||||
|
" millis, whole test time: " + (System.nanoTime() - t0) / 1000_000 +
|
||||||
|
" millis"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int tid;
|
||||||
|
OOMEInAQS(int tid) {
|
||||||
|
this.tid = tid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
int id = tid, nextId = (id + 1) % NTHREADS;
|
||||||
|
final ReentrantLock lock = mainLock;
|
||||||
|
final Condition cond = condition;
|
||||||
|
try {
|
||||||
|
started.await();
|
||||||
|
for (int i = 0; i < NREPS; i++) {
|
||||||
|
try {
|
||||||
|
lock.lock();
|
||||||
|
while (turn != id)
|
||||||
|
cond.await();
|
||||||
|
turn = nextId;
|
||||||
|
cond.signalAll();
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
if (i == 2) // Subsequent AQS methods encounter OOME
|
||||||
|
filled.await();
|
||||||
|
}
|
||||||
|
} catch (Throwable ex) { // Could be InterruptedExeption or OOME
|
||||||
|
data = null;
|
||||||
|
System.exit(0); // avoid getting stuck trying to recover
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Object[] fillHeap() {
|
||||||
|
Object[] first = null, last = null;
|
||||||
|
int size = 1 << 20;
|
||||||
|
while (size > 0) {
|
||||||
|
try {
|
||||||
|
Object[] array = new Object[size];
|
||||||
|
if (first == null) {
|
||||||
|
first = array;
|
||||||
|
} else {
|
||||||
|
last[0] = array;
|
||||||
|
}
|
||||||
|
last = array;
|
||||||
|
} catch (OutOfMemoryError oome) {
|
||||||
|
size = size >>> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue