Name: rs12567 Date: 08/05/97
Attn: Sheng Liang
This is the same bug as bug no. 4041699
-
There is more to this problem than we are covering here (i.e.
there are more class object locking issues in the VM), and
Javasoft had previously decided that it was too dangerous to
change it now, because it needs a total re-write. I hope that
I convinced Sheng Liang that as the code is now, synchronized
static methods are just not safe, and that the situation can be
considerably improved with a very limited change. The code of
IBM's San Francisco project uses a lot of synchronized static
methods, and so JVM hangs have been a real problem for them.
It seems a bit silly that they should have to change all their
synchronized static methods to lock some other object when the
fault lies with the JVM.
-
I have plenty of information about this problem, and I can
easily generate more test cases, so if there is any doubt,
please do not hesitate to contact me (###@###.###).
This is a serious problem!!
---
Class objects are locked in five places in the JVM (this applies
to all 1.1.x versions).
-
classinitialize.c:
Place #1. ResolveClassConstantFromClass
Place #2. ResolveClassConstant
These both lock the class from which opcodes are being
interpreted when certain opcodes are resolved to _quick
opcodes.
-
classresolver.c:
Place #3. makeslottable
-
classresolver.c:
Place #4. InitializeClass
Place #5. ResolveClass
These methods are both called during the initialization of a
newly loaded class, and they can be called under another
class lock from ResolveClassConstant and
ResolveClassConstantFromClass (even if the class is not
newly loaded).
They lock the class they're initializing.
-
Test case (Deadlock1.java):
-
class D1A {
static int x;
// Locks D1A
static synchronized void foo() {
try {
Thread.sleep(500);
System.out.println("D1A test");
D1B.x = 1; /* Locks D1B through ResolveClassConstant */
}
catch (Exception e) {}
}
}
class D1B {
static int x;
static synchronized void foo() {
try {
Thread.sleep(500);
System.out.println("D1B test");
D1A.x = 1; /* Locks D1A through ResolveClassConstant */
}
catch (Exception e) {}
}
}
class ThreadD1A implements Runnable {
public void run() {
D1A.foo();
}
}
class ThreadD1B implements Runnable {
public void run() {
D1B.foo();
}
}
public class Deadlock1 {
public static void main(String[] args)
{
System.out.println("This test passes if it doesn't hang");
new Thread(new ThreadD1A(), "Thread-A").start();
new Thread(new ThreadD1B(), "Thread-B").start();
}
}
What's happening here?
The class is locked during the synchronized static method. Each
method then makes a reference to the other method. The JVM locks
the other method's class while it resolves this reference.
Because two threads are doing this at once, there is a deadlock.
-
SUGGESTED FIX
SAS has done some research on this problem, and they came up
with a very similar fix to mine (Robert Field at Javasoft has
details about their fix). This fix makes the class locking
work in an analogous way to JDK1.0.2. JDK1.0.2 used a
different lock for classes within the VM from the lock used for
synchronized methods.
-
This is what we suggest. This should be pretty much exactly the
same as SAS's suggestion (but I don't have access to their
notes):
-
FILE *** src/share/java/runtime/classinitialize.c
--- FROM (ResolveClassConstantFromClass function)
monitorEnter(obj_monitor(class));
ret = Locked_ResolveClassConstant(class, constant_pool,
index, ee, mask);
monitorExit(obj_monitor(class));
--- TO
monitorEnter(obj_monitor(class)+1);
ret = Locked_ResolveClassConstant(class, constant_pool,
index, ee, mask);
monitorExit(obj_monitor(class)+1);
--- END
--- FROM (ResolveClassConstant function)
monitorEnter(obj_monitor(class));
ret = Locked_ResolveClassConstant(class, constant_pool,
index, ee, mask);
monitorExit(obj_monitor(class));
--- TO
monitorEnter(obj_monitor(class)+1);
ret = Locked_ResolveClassConstant(class, constant_pool,
index, ee, mask);
monitorExit(obj_monitor(class)+1);
--- END
-
FILE *** src/share/java/runtime/classresolver.c
Note: here we are using the class object handle address + 2
instead of + 1 in makeslottable. SAS has a justification for
this, so please talk to Robert Field about it.
--- FROM (makeslottable function)
monitorEnter(obj_monitor(clb));
result = Locked_makeslottable(clb);
monitorExit(obj_monitor(clb));
--- TO
monitorEnter(obj_monitor(clb)+2);
result = Locked_makeslottable(clb);
monitorExit(obj_monitor(clb)+2);
--- END
--- FROM (InitializeClass function)
monitorEnter(obj_monitor(cb));
result = Locked_InitializeClass(cb, detail);
monitorExit(obj_monitor(cb));
--- TO
monitorEnter(obj_monitor(cb)+1);
result = Locked_InitializeClass(cb, detail);
monitorExit(obj_monitor(cb)+1);
--- END
--- FROM (ResolveClass function)
monitorEnter(obj_monitor(cb));
result = Locked_ResolveClass(cb, detail);
monitorExit(obj_monitor(cb));
--- TO
monitorEnter(obj_monitor(cb)+1);
result = Locked_ResolveClass(cb, detail);
monitorExit(obj_monitor(cb)+1);
--- END
-
FILE *** src/share/java/runtime/gc.c
monitorDumpHelper can be improved:
--- FROM
void
monitorDumpHelper(monitor_t *mid, void *arg)
{
unsigned int key = mid->key;
bool_t verbose = (bool_t) arg;
SetLimits();
if (verbose != FALSE || mid->use_count != 0) {
if (ValidHandle(key)) {
jio_fprintf(stderr, " %s: ", Object2CString((JHandle *) key));
} else {
jio_fprintf(stderr, " <unknown key> (0x%lx): ", key);
}
sysMonitorDumpInfo((sys_mon_t *)&mid->mid);
}
}
--- TO
void
monitorDumpHelper(monitor_t *mid, void *arg)
{
unsigned int key = mid->key & ~0x03;
unsigned int offset = mid->key & 0x03;
bool_t verbose = (bool_t) arg;
SetLimits();
if (verbose != FALSE || mid->use_count != 0) {
if (ValidHandle(key)) {
if (offset)
jio_fprintf(stderr, " %s+%d: ", Object2CString((JHandle *) key),
offset);
else
jio_fprintf(stderr, " %s: ", Object2CString((JHandle *) key));
} else {
jio_fprintf(stderr, " <unknown key> (0x%lx): ", key);
}
sysMonitorDumpInfo((sys_mon_t *)&mid->mid);
}
}
--- END
======================================================================