8156500: Move Reference pending list into VM to prevent deadlocks

Move reference pending list and locking into VM

Co-authored-by: Per Liden <per.liden@oracle.com>
Reviewed-by: coleenp, dholmes, dcubed, mchung, plevart
This commit is contained in:
Kim Barrett 2016-08-30 23:46:02 -04:00
parent 7c219b82fc
commit 7bddef3ed0
7 changed files with 185 additions and 116 deletions

View File

@ -176,6 +176,9 @@ SUNWprivate_1.1 {
Java_java_lang_ProcessHandleImpl_00024Info_info0;
Java_java_lang_ProcessImpl_init;
Java_java_lang_ProcessImpl_forkAndExec;
Java_java_lang_ref_Reference_getAndClearReferencePendingList;
Java_java_lang_ref_Reference_hasReferencePendingList;
Java_java_lang_ref_Reference_waitForReferencePendingList;
Java_java_lang_reflect_Array_get;
Java_java_lang_reflect_Array_getBoolean;
Java_java_lang_reflect_Array_getByte;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2016, 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
@ -110,22 +110,6 @@ public abstract class Reference<T> {
private transient Reference<T> discovered; /* used by VM */
/* Object used to synchronize with the garbage collector. The collector
* must acquire this lock at the beginning of each collection cycle. It is
* therefore critical that any code holding this lock complete as quickly
* as possible, allocate no new objects, and avoid calling user code.
*/
private static class Lock { }
private static Lock lock = new Lock();
/* List of References waiting to be enqueued. The collector adds
* References to this list, while the Reference-handler thread removes
* them. This list is protected by the above lock object. The
* list uses the discovered field to link its elements.
*/
private static Reference<Object> pending = null;
/* High-priority thread to enqueue pending References
*/
private static class ReferenceHandler extends Thread {
@ -139,10 +123,9 @@ public abstract class Reference<T> {
}
static {
// pre-load and initialize InterruptedException and Cleaner classes
// so that we don't get into trouble later in the run loop if there's
// memory shortage while loading/initializing them lazily.
ensureClassInitialized(InterruptedException.class);
// pre-load and initialize Cleaner class so that we don't
// get into trouble later in the run loop if there's
// memory shortage while loading/initializing it lazily.
ensureClassInitialized(Cleaner.class);
}
@ -152,72 +135,80 @@ public abstract class Reference<T> {
public void run() {
while (true) {
tryHandlePending(true);
processPendingReferences();
}
}
}
/**
* Try handle pending {@link Reference} if there is one.<p>
* Return {@code true} as a hint that there might be another
* {@link Reference} pending or {@code false} when there are no more pending
* {@link Reference}s at the moment and the program can do some other
* useful work instead of looping.
*
* @param waitForNotify if {@code true} and there was no pending
* {@link Reference}, wait until notified from VM
* or interrupted; if {@code false}, return immediately
* when there is no pending {@link Reference}.
* @return {@code true} if there was a {@link Reference} pending and it
* was processed, or we waited for notification and either got it
* or thread was interrupted before being notified;
* {@code false} otherwise.
/* Atomically get and clear (set to null) the VM's pending list.
*/
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
// 'instanceof' might throw OutOfMemoryError sometimes
// so do this before un-linking 'r' from the 'pending' chain...
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink 'r' from 'pending' chain
pending = r.discovered;
r.discovered = null;
} else {
// The waiting on the lock may cause an OutOfMemoryError
// because it may try to allocate exception objects.
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
private static native Reference<Object> getAndClearReferencePendingList();
/* Test whether the VM's pending list contains any entries.
*/
private static native boolean hasReferencePendingList();
/* Wait until the VM's pending list may be non-null.
*/
private static native void waitForReferencePendingList();
private static final Object processPendingLock = new Object();
private static boolean processPendingActive = false;
private static void processPendingReferences() {
// Only the singleton reference processing thread calls
// waitForReferencePendingList() and getAndClearReferencePendingList().
// These are separate operations to avoid a race with other threads
// that are calling waitForReferenceProcessing().
waitForReferencePendingList();
Reference<Object> pendingList;
synchronized (processPendingLock) {
pendingList = getAndClearReferencePendingList();
processPendingActive = true;
}
while (pendingList != null) {
Reference<Object> ref = pendingList;
pendingList = ref.discovered;
ref.discovered = null;
if (ref instanceof Cleaner) {
((Cleaner)ref).clean();
// Notify any waiters that progress has been made.
// This improves latency for nio.Bits waiters, which
// are the only important ones.
synchronized (processPendingLock) {
processPendingLock.notifyAll();
}
} else {
ReferenceQueue<? super Object> q = ref.queue;
if (q != ReferenceQueue.NULL) q.enqueue(ref);
}
} catch (OutOfMemoryError x) {
// Give other threads CPU time so they hopefully drop some live references
// and GC reclaims some space.
// Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
// persistently throws OOME for some time...
Thread.yield();
// retry
return true;
} catch (InterruptedException x) {
// retry
return true;
}
// Fast path for cleaners
if (c != null) {
c.clean();
return true;
// Notify any waiters of completion of current round.
synchronized (processPendingLock) {
processPendingActive = false;
processPendingLock.notifyAll();
}
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
// Wait for progress in reference processing.
//
// Returns true after waiting (for notification from the reference
// processing thread) if either (1) the VM has any pending
// references, or (2) the reference processing thread is
// processing references. Otherwise, returns false immediately.
private static boolean waitForReferenceProcessing()
throws InterruptedException
{
synchronized (processPendingLock) {
if (processPendingActive || hasReferencePendingList()) {
// Wait for progress, not necessarily completion.
processPendingLock.wait();
return true;
} else {
return false;
}
}
}
static {
@ -236,8 +227,10 @@ public abstract class Reference<T> {
// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
public boolean waitForReferenceProcessing()
throws InterruptedException
{
return Reference.waitForReferenceProcessing();
}
});
}

View File

@ -131,23 +131,38 @@ class Bits { // package-private
}
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()) {
if (tryReserveMemory(size, cap)) {
return;
}
}
// 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;
try {
// Retry allocation until success or there are no more
// references (including Cleaners that might free direct
// buffer memory) to process and allocation still fails.
boolean refprocActive;
do {
try {
refprocActive = jlra.waitForReferenceProcessing();
} catch (InterruptedException e) {
// Defer interrupts and keep trying.
interrupted = true;
refprocActive = true;
}
if (tryReserveMemory(size, cap)) {
return;
}
} while (refprocActive);
// trigger VM's Reference processing
System.gc();
// A retry loop with exponential back-off delays.
// Sometimes it would suffice to give up once reference
// processing is complete. But if there are many threads
// competing for memory, this gives more opportunities for
// any given thread to make progress. In particular, this
// seems to be enough for a stress test like
// DirectBufferAllocTest to (usually) succeed, while
// without it that test likely fails. Since failure here
// ends in OOME, there's no need to hurry.
long sleepTime = 1;
int sleeps = 0;
while (true) {
@ -157,14 +172,14 @@ class Bits { // package-private
if (sleeps >= MAX_SLEEPS) {
break;
}
if (!jlra.tryHandlePendingReference()) {
try {
try {
if (!jlra.waitForReferenceProcessing()) {
Thread.sleep(sleepTime);
sleepTime <<= 1;
sleeps++;
} catch (InterruptedException e) {
interrupted = true;
}
} catch (InterruptedException e) {
interrupted = true;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2016, 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
@ -28,12 +28,12 @@ package jdk.internal.misc;
public interface JavaLangRefAccess {
/**
* Help ReferenceHandler thread process next pending
* {@link java.lang.ref.Reference}
* Wait for progress in {@link java.lang.ref.Reference}
* processing. If there aren't any pending {@link
* java.lang.ref.Reference}s, return immediately.
*
* @return {@code true} if there was a pending reference and it
* was enqueue-ed or {@code false} if there was no
* pending reference
* @return {@code true} if there were any pending
* {@link java.lang.ref.Reference}s, {@code false} otherwise.
*/
boolean tryHandlePendingReference();
boolean waitForReferenceProcessing() throws InterruptedException;
}

View File

@ -281,6 +281,18 @@ JVM_GetSystemPackage(JNIEnv *env, jstring name);
JNIEXPORT jobjectArray JNICALL
JVM_GetSystemPackages(JNIEnv *env);
/*
* java.lang.ref.Reference
*/
JNIEXPORT jobject JNICALL
JVM_GetAndClearReferencePendingList(JNIEnv *env);
JNIEXPORT jboolean JNICALL
JVM_HasReferencePendingList(JNIEnv *env);
JNIEXPORT void JNICALL
JVM_WaitForReferencePendingList(JNIEnv *env);
/*
* java.io.ObjectInputStream
*/

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2016, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
#include "jvm.h"
#include "java_lang_ref_Reference.h"
JNIEXPORT jobject JNICALL
Java_java_lang_ref_Reference_getAndClearReferencePendingList(JNIEnv *env, jclass ignore)
{
return JVM_GetAndClearReferencePendingList(env);
}
JNIEXPORT jboolean JNICALL
Java_java_lang_ref_Reference_hasReferencePendingList(JNIEnv *env, jclass ignore)
{
return JVM_HasReferencePendingList(env);
}
JNIEXPORT void JNICALL
Java_java_lang_ref_Reference_waitForReferencePendingList(JNIEnv *env, jclass ignore)
{
JVM_WaitForReferencePendingList(env);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2016, 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
@ -69,7 +69,7 @@ public class FileInputStreamPoolTest {
// make JVM process References
System.gc();
// help ReferenceHandler thread enqueue References
while (TestProxy.Reference_tryHandlePending(false)) {}
while (TestProxy.Reference_waitForReferenceProcessing()) { }
// help run Finalizers
System.runFinalization();
}
@ -103,11 +103,11 @@ public class FileInputStreamPoolTest {
/**
* A proxy for (package)private static methods:
* sun.security.provider.FileInputStreamPool.getInputStream
* java.lang.ref.Reference.tryHandlePending
* java.lang.ref.Reference.waitForReferenceProcessing
*/
static class TestProxy {
private static final Method getInputStreamMethod;
private static final Method tryHandlePendingMethod;
private static final Method waitForReferenceProcessingMethod;
static {
try {
@ -118,9 +118,9 @@ public class FileInputStreamPoolTest {
"getInputStream", File.class);
getInputStreamMethod.setAccessible(true);
tryHandlePendingMethod = Reference.class.getDeclaredMethod(
"tryHandlePending", boolean.class);
tryHandlePendingMethod.setAccessible(true);
waitForReferenceProcessingMethod =
Reference.class.getDeclaredMethod("waitForReferenceProcessing");
waitForReferenceProcessingMethod.setAccessible(true);
} catch (Exception e) {
throw new Error(e);
}
@ -146,13 +146,14 @@ public class FileInputStreamPoolTest {
}
}
static boolean Reference_tryHandlePending(boolean waitForNotify) {
static boolean Reference_waitForReferenceProcessing() {
try {
return (boolean) tryHandlePendingMethod
.invoke(null, waitForNotify);
return (boolean) waitForReferenceProcessingMethod.invoke(null);
} catch (InvocationTargetException e) {
Throwable te = e.getTargetException();
if (te instanceof RuntimeException) {
if (te instanceof InterruptedException) {
return true;
} else if (te instanceof RuntimeException) {
throw (RuntimeException) te;
} else if (te instanceof Error) {
throw (Error) te;