Захват статического блока Java с помощью System.exit

Этот код будет заблокирован:

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) {}
}

Этот код будет нормально работать:

public class Main {
   static public final Object a = new Object();
   static {
      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) {}
}

Что происходит?

Ответ 1

Важно, чтобы классы не обращались одновременно во время инициализации, поэтому блокировка сохраняется.

Я предполагаю, что происходит в первом случае:

  • Основной поток содержит блокировку инициализации для Main.
  • При удерживании блокировки System.exit блокируется, так как он не возвращается.
  • Выполняется выполнение команды завершения работы.
  • Завершение работы пытается получить доступ к классу Main для чтения поля, но блокировки по мере инициализации класса.

Отсюда тупик. Это немного яснее, если вы пишете if (a == null); как if (Main.a == null);.

Во втором случае это значение копируется, и поэтому кверт завершения работы не нуждается в доступе к классу Main.

Мораль: не смешивать потоки и инициализацию класса. Книга Gafter и Bloch Java Puzzlers имеет больше об этом.

Ответ 2

Здесь байт-код для запираемого примера:

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

}

И здесь байт-код для случая, который заканчивается обычно:

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

}

Байт-код явно отличается. Я либо придумаю ответ, либо кто-то другой, кто понимает, что внутренняя часть JVM поможет.

Ответ 3

Я запустил ту же программу с IBM JVM (она сбрасывает больше информации о "kill -QUIT" ). Рамки в дампе потока объясняют комментарий Тома Хокина:

Я переименовал Main в StaticBlockShutdownHook. Как вы можете видеть ниже, основной затвор инициализации (монитор) удерживался основным потоком во время выполнения статического блока(), и поток Shutdown Hook ожидает уведомления на этом мониторе.

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)

Раздел "мониторы" показывает, что блокировки относятся к классу Main. J9VMINternals.initializeImpl(), по-видимому, является родным методом, который захватывает объект класса.

1LKMONPOOLDUMP Monitor Pool Dump (flat & inflated object-monitors):
2LKMONINUSE      sys_mon_t:0x0000000110FF38D0 infl_mon_t: 0x0000000110FF3910:
3LKMONOBJECT       [email protected]/0x07000000000DB7E0: <unowned>
3LKNOTIFYQ            Waiting to be notified:
3LKWAITNOTIFY            "Thread-5" (0x0000000112C5AF00)
2LKMONINUSE      sys_mon_t:0x0000000112687AA0 infl_mon_t: 0x0000000112687AE0:
3LKMONOBJECT       [email protected]/0x07000000000D0398: <unowned>
3LKNOTIFYQ            Waiting to be notified:
3LKWAITNOTIFY            "main" (0x0000000110F5FE00)

Ответ 4

У меня аналогичный тупик с spring с моим крючком остановки.

Я думаю, это может быть ошибка в JVM? Вы можете проверить его

"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)

Отключительный поток, ожидающий блокировки объекта, который заблокирован сам по себе.

Благодарим вас за информацию, что удаление ссылки на внешние объекты может помочь разрешить блокировку.