mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-05 05:30:45 +00:00
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:
parent
7c219b82fc
commit
7bddef3ed0
@ -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;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
45
jdk/src/java.base/share/native/libjava/Reference.c
Normal file
45
jdk/src/java.base/share/native/libjava/Reference.c
Normal 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);
|
||||
}
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user