开发者

Java Static-block Shutdown Hook with System.exit

开发者 https://www.devze.com 2022-12-20 11:50 出处:网络
This code will deadlock: public class Main { static public final Object a = new Object(); static { Runtime.getRuntime().addShutdownHook(new Thread() {

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消