Java Static-block Shutdown Hook with System.exit
This code will deadlock:
public class Main {
static public final Object a = new Object();
static {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() { if (a == null); }
});
System.exit(0);
}
static public void main(final String[] args) {}
}
This code will exit normally:
public class Main {
static public final Object a = new Object();
stat开发者_开发知识库ic {
final Object aa = a;
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() { if (aa == null); }
});
System.exit(0);
}
static public void main(final String[] args) {}
}
What is happening?
It is important that classes are not accessed concurrently whilst initialising, so a lock is held.
I guess what is happening in the first case is:
- The main thread holds the initialisation lock for
Main
. - Whilst holding the lock,
System.exit
blocks as it does not return. - The shutdown hook executes.
- The shutdown tries to access the
Main
class to read a field, but blocks as the class is initialising.
Hence the deadlock. It's a little clearer if you write if (a == null);
as if (Main.a == null);
.
In the second case, the value is copied and therefore the shutdown hook does not need to access the Main
class.
Moral: Don't mix threads and class initialisation. Gafter and Bloch's Java Puzzlers book has more on this.
Here's the bytecode for the deadlocked example:
public class Main extends java.lang.Object{
public static final java.lang.Object a;
public Main();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: return
static {};
Code:
0: new #2; //class java/lang/Object
3: dup
4: invokespecial #1; //Method java/lang/Object."<init>":()V
7: putstatic #3; //Field a:Ljava/lang/Object;
10: invokestatic #4; //Method java/lang/Runtime.getRuntime:()Ljava/lang/Runtime;
13: new #5; //class Main$1
16: dup
17: invokespecial #6; //Method Main$1."<init>":()V
20: invokevirtual #7; //Method java/lang/Runtime.addShutdownHook:(Ljava/lang/Thread;)V
23: iconst_0
24: invokestatic #8; //Method java/lang/System.exit:(I)V
27: return
}
And here's the bytecode for the case that finishes normally:
public class Main extends java.lang.Object{
public static final java.lang.Object a;
public Main();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: return
static {};
Code:
0: new #2; //class java/lang/Object
3: dup
4: invokespecial #1; //Method java/lang/Object."<init>":()V
7: putstatic #3; //Field a:Ljava/lang/Object;
10: getstatic #3; //Field a:Ljava/lang/Object;
13: astore_0
14: invokestatic #4; //Method java/lang/Runtime.getRuntime:()Ljava/lang/Runtime;
17: new #5; //class Main$1
20: dup
21: aload_0
22: invokespecial #6; //Method Main$1."<init>":(Ljava/lang/Object;)V
25: invokevirtual #7; //Method java/lang/Runtime.addShutdownHook:(Ljava/lang/Thread;)V
28: iconst_0
29: invokestatic #8; //Method java/lang/System.exit:(I)V
32: return
}
The bytecode is obviously different. I'll either come up with the answer or someone else who understands the internals of the JVM will help.
I ran the same program with IBM JVM (it dumps more information on a 'kill -QUIT'). The frames in thread dump explains Tom Hawkin's comment:
I renamed Main to StaticBlockShutdownHook. As you can see below, an internal initialization lock (monitor) has been held by the main thread during execution of static block () and the Shutdown Hook thread is waiting to be notified on this monitor.
3XMTHREADINFO "main" J9VMThread:0x0000000110F5FE00, j9thread_t:0x000000011014E500, java/lang/Thread:0x0700000000002330, state:CW, prio=5
3XMTHREADINFO1 (native thread ID:0x5A10083, native priority:0x5, native policy:UNKNOWN)
3XMTHREADINFO3 Java callstack:
4XESTACKTRACE at java/lang/Object.wait(Native Method)
4XESTACKTRACE at java/lang/Object.wait(Object.java:196)
4XESTACKTRACE at java/lang/Thread.join(Thread.java:616)
4XESTACKTRACE at java/lang/ApplicationShutdownHooks.run(ApplicationShutdownHooks.java:91)
4XESTACKTRACE at java/lang/Shutdown.runHooks(Shutdown.java:101)
4XESTACKTRACE at java/lang/Shutdown.sequence(Shutdown.java:145)
4XESTACKTRACE at java/lang/Shutdown.exit(Shutdown.java:190)
4XESTACKTRACE at java/lang/Runtime.exit(Runtime.java:101)
4XESTACKTRACE at java/lang/System.exit(System.java:279)
4XESTACKTRACE at StaticBlockShutdownHook.<clinit>(StaticBlockShutdownHook.java:11)
4XESTACKTRACE at java/lang/J9VMInternals.initializeImpl(Native Method)
4XESTACKTRACE at java/lang/J9VMInternals.initialize(J9VMInternals.java:200(Compiled Code))
3XMTHREADINFO "Thread-5" J9VMThread:0x0000000112C5AF00, j9thread_t:0x0000000112BE01C0, java/lang/Thread:0x07000000000D0380, state:CW, prio=5
3XMTHREADINFO1 (native thread ID:0x23B00BD, native priority:0x5, native policy:UNKNOWN)
3XMTHREADINFO3 Java callstack:
4XESTACKTRACE at java/lang/Object.wait(Native Method)
4XESTACKTRACE at java/lang/Object.wait(Object.java:167(Compiled Code))
4XESTACKTRACE at java/lang/J9VMInternals.initialize(J9VMInternals.java:130(Compiled Code))
4XESTACKTRACE at StaticBlockShutdownHook$1.run(StaticBlockShutdownHook.java:7)
The "monitors" section shows that the locks are of the Main class. The J9VMINternals.initializeImpl() seems to be a native method which takes the Class object lock.
1LKMONPOOLDUMP Monitor Pool Dump (flat & inflated object-monitors):
2LKMONINUSE sys_mon_t:0x0000000110FF38D0 infl_mon_t: 0x0000000110FF3910:
3LKMONOBJECT StaticBlockThread@0x07000000000DB7C8/0x07000000000DB7E0: <unowned>
3LKNOTIFYQ Waiting to be notified:
3LKWAITNOTIFY "Thread-5" (0x0000000112C5AF00)
2LKMONINUSE sys_mon_t:0x0000000112687AA0 infl_mon_t: 0x0000000112687AE0:
3LKMONOBJECT StaticBlockThread$1@0x07000000000D0380/0x07000000000D0398: <unowned>
3LKNOTIFYQ Waiting to be notified:
3LKWAITNOTIFY "main" (0x0000000110F5FE00)
I am having a similar deadlock with spring with my shutdown hook.
I guess it could be a bug in JVM? Can you verify it
"main" prio=6 tid=0x00316800 nid=0x52c in Object.wait() [0x0093f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x22a19da8> (a Main2$1)
at java.lang.Thread.join(Thread.java:1143)
- locked <0x22a19da8> (a Main2$1)
at java.lang.Thread.join(Thread.java:1196)
at java.lang.ApplicationShutdownHooks.runHooks(ApplicationShutdownHooks.java:79)
The shutdown hook thread waiting for a object lock which is locked by itself.
Thanks for you information that removal of external object reference may help to resolve the lock.
精彩评论