Java ShutdownHook не может присоединиться к основному потоку при запуске из JNI - PullRequest
0 голосов
/ 11 октября 2018

У меня есть некоторый Java-код для создания перехвата отключения, чтобы он мог чисто завершиться, когда клиент нажимает Ctrl + C:

private static void shutdownHandler(Thread mainThread) {
    try {
        mainThread.join(30000);
    } catch (InterruptedException e) {
    }
}

public static void main(String[] args) {
    final Thread mainThread = Thread.currentThread();
    Thread shutdownThread = new Thread(() -> shutdownHandler(mainThread));
    Runtime.getRuntime().addShutdownHook(shutdownThread);
}

Когда я запускаю это из командной строки, он работает как положено (основной поток завершается и почти сразу возвращается в командную строку).Тем не менее, если я напишу оболочку JNI, которая вызывает ее, используя следующий код C ++:

JavaVMInitArgs vm_args;
// Populate vm_args

JavaVM *jvm;
JNIEnv *env;
JNI_CreateJavaVM(&jvm, reinterpret_cast<void**>(&env), &vm_args);

jclass mainClass = env->FindClass("path/to/my/class");
jmethod mainMethod = env->GetStaticMethodID(mainClass, "main", "([L" STRING_CLASS ";)V");

jclass stringClass = env->FindClass(STRING_CLASS);
jobjectArray mainArgs = env->NewObjectArray(0, stringClass, NULL);

env->CallStaticVoidMethod(mainClass, mainMethod, mainArgs);
jvm->DestroyJavaVM();

Затем метод shutdownHandler зависает до истечения 30-секундного тайм-аута, затем возвращает управление коду C ++ и, в конце концов,выходы.Кто-нибудь знает способ, позволяющий методу shutdownHandler присоединиться к основному потоку при запуске из вызова JNI?

1 Ответ

0 голосов
/ 11 октября 2018

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

Во втором варианте основной поток, т. Е. Поток, который выполнил метод main через env -> CallStaticVoidMethod(…), занят выполнением jvm -> DestroyJavaVM().Поскольку эта функция ожидает завершения работы обработчиков завершения работы, а ваш обработчик завершения работы ожидает завершения работы этого потока, у вас есть тупик.

Вы можете получить аналогичное поведение и с чистым кодом Java.Когда вы помещаете System.exit(0); в конец метода main, позволяя основному потоку инициировать завершение работы и ждать его завершения, вы получаете аналогичную тупиковую ситуацию.

Как правило, вы не должны выполнять joinработа в обработчиках отключения.Эти обработчики должны очистить и вернуть как можно быстрее.

Или, как указывает документация :

Отключающие крюки запускаются в деликатное времяв жизненном цикле виртуальной машины и, следовательно, должны быть защищены.В частности, они должны быть написаны так, чтобы они были поточно-ориентированными и, по возможности, избегали взаимоблокировок.Они также не должны слепо полагаться на службы, которые могли зарегистрировать свои собственные крюки отключения и, следовательно, могут сами в процессе выключения.Попытки использовать другие основанные на потоке сервисы, такие как поток обработки событий AWT, например, могут привести к взаимоблокировкам.

...