187 lines
6.6 KiB
Java
187 lines
6.6 KiB
Java
|
/*
|
||
|
* Copyright (c) 2018, 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.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* @test
|
||
|
* @bug 4087516
|
||
|
* @summary Incorrect locking leads to deadlock in monitorCacheMaybeExpand.
|
||
|
* @author Anand Palaniswamy
|
||
|
* @build MonitorCacheMaybeExpand_DeadLock
|
||
|
* @run main/othervm MonitorCacheMaybeExpand_DeadLock
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Background on the bug:
|
||
|
*
|
||
|
* The thread local monitor cache had a locking bug (till
|
||
|
* 1.2beta1) where two threads trying to expand the monitor cache
|
||
|
* at the same time would cause deadlock. The code paths that the
|
||
|
* two threads must be executing for this to happen is described
|
||
|
* in the bug report.
|
||
|
*
|
||
|
* Caveat and red-flag:
|
||
|
*
|
||
|
* Since deadlocks are very timing dependent, there is a good
|
||
|
* chance this test case will not catch the bug most of the time
|
||
|
* -- on your machine and setting, it is _possible_ that the two
|
||
|
* threads might not try a monitorCacheExpand at the same
|
||
|
* time. But in practice, on Solaris native threads, this program
|
||
|
* deadlocks the VM in about 2 seconds pretty consistently,
|
||
|
* whether MP or not.
|
||
|
*
|
||
|
* The rationale for running this test despite this rather large
|
||
|
* caveat is that at worst, it can do no harm.
|
||
|
*
|
||
|
* The idea:
|
||
|
*
|
||
|
* Is to create two monitor hungry threads.
|
||
|
*
|
||
|
* Originally Tom Rodriguez and I suspected that this weird state
|
||
|
* of two threads trying to expand monitor cache can happen only
|
||
|
* if:
|
||
|
*
|
||
|
* Thread 1: Is in the middle of a monitorCacheMaybeExpand.
|
||
|
* Thread 2: Runs GC and tries to freeClasses(). This causes
|
||
|
* sysFree() to be invoked, which in turn needs a
|
||
|
* mutex_lock -- and oops, we end up deadlocking
|
||
|
* with 1 on green_threads.
|
||
|
*
|
||
|
* Which is why this test tries to cause class GC at regular
|
||
|
* intervals.
|
||
|
*
|
||
|
* Turns out that the GC is not required. Two instances of the
|
||
|
* monitor hungry threads deadlock the VM pretty quick. :-) Infact
|
||
|
* the static initializer in the forName'd classes running
|
||
|
* alongside one of the hungry threads is sufficient to
|
||
|
* deadlock. Still keep the GC stuff just-in-case (and also
|
||
|
* because I wrote it :-).
|
||
|
*
|
||
|
*/
|
||
|
public class MonitorCacheMaybeExpand_DeadLock {
|
||
|
|
||
|
/**
|
||
|
* A monitor-hungry thread.
|
||
|
*/
|
||
|
static class LotsaMonitors extends Thread {
|
||
|
|
||
|
/** How many recursions? Could cause Java stack overflow. */
|
||
|
static final int MAX_DEPTH = 800;
|
||
|
|
||
|
/** What is our depth? */
|
||
|
int depth = 0;
|
||
|
|
||
|
/** Thread ID */
|
||
|
int tid;
|
||
|
|
||
|
/** So output will have thread number. */
|
||
|
public LotsaMonitors(int tid, int depth) {
|
||
|
super("LotsaMonitors #" + new Integer(tid).toString());
|
||
|
this.tid = tid;
|
||
|
this.depth = depth;
|
||
|
}
|
||
|
|
||
|
/** Start a recursion that grabs monitors. */
|
||
|
public void run() {
|
||
|
System.out.println(">>>Starting " + this.toString() + " ...");
|
||
|
Thread.currentThread().yield();
|
||
|
this.recurse();
|
||
|
System.out.println("<<<Finished " + this.toString());
|
||
|
}
|
||
|
|
||
|
/** Every call to this method grabs an extra monitor. */
|
||
|
synchronized void recurse() {
|
||
|
if (this.depth > 0) {
|
||
|
new LotsaMonitors(tid, depth-1).recurse();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The test.
|
||
|
*/
|
||
|
public static void main(String[] args) {
|
||
|
/* Start the two of these crazy threads. */
|
||
|
new LotsaMonitors(1, LotsaMonitors.MAX_DEPTH).start();
|
||
|
new LotsaMonitors(2, LotsaMonitors.MAX_DEPTH).start();
|
||
|
|
||
|
/* And sit there and GC for good measure. */
|
||
|
for (int i = 0; i < MAX_GC_ITERATIONS; i++) {
|
||
|
new LotsaMonitors(i+3, LotsaMonitors.MAX_DEPTH).start();
|
||
|
System.out.println(">>>Loading 10 classes and gc'ing ...");
|
||
|
Class[] classes = new Class[10];
|
||
|
fillClasses(classes);
|
||
|
classes = null;
|
||
|
System.gc();
|
||
|
Thread.currentThread().yield();
|
||
|
System.out.println("<<<Finished loading 10 classes and gc'ing");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** How many times to GC? */
|
||
|
static final int MAX_GC_ITERATIONS = 10;
|
||
|
|
||
|
/** Load some classes into the array. */
|
||
|
static void fillClasses(Class[] classes) {
|
||
|
for (int i = 0; i < classes.length; i++) {
|
||
|
try {
|
||
|
classes[i] = Class.forName(classnames[i]);
|
||
|
} catch (ClassNotFoundException cnfe) {
|
||
|
cnfe.printStackTrace();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Some random classes to load. */
|
||
|
private static String[] classnames = {
|
||
|
"java.text.DecimalFormat",
|
||
|
"java.text.MessageFormat",
|
||
|
"java.util.GregorianCalendar",
|
||
|
"java.util.ResourceBundle",
|
||
|
"java.text.Collator",
|
||
|
"java.util.Date",
|
||
|
"java.io.Reader",
|
||
|
"java.io.Writer",
|
||
|
"java.lang.IllegalAccessException",
|
||
|
"java.lang.InstantiationException",
|
||
|
"java.lang.ClassNotFoundException",
|
||
|
"java.lang.CloneNotSupportedException",
|
||
|
"java.lang.InterruptedException",
|
||
|
"java.lang.NoSuchFieldException",
|
||
|
"java.lang.NoSuchMethodException",
|
||
|
"java.lang.RuntimeException",
|
||
|
"java.lang.ArithmeticException",
|
||
|
"java.lang.ArrayStoreException",
|
||
|
"java.lang.ClassCastException",
|
||
|
"java.lang.StringIndexOutOfBoundsException",
|
||
|
"java.lang.NegativeArraySizeException",
|
||
|
"java.lang.IllegalStateException",
|
||
|
"java.lang.IllegalArgumentException",
|
||
|
"java.lang.NumberFormatException",
|
||
|
"java.lang.IllegalThreadStateException",
|
||
|
"java.lang.IllegalMonitorStateException",
|
||
|
"java.lang.SecurityException",
|
||
|
"java.lang.ExceptionInInitializerError"
|
||
|
};
|
||
|
|
||
|
}
|