从JNI运行时,Java ShutdownHook无法加入主线程

时间:2018-10-11 00:33:31

标签: java multithreading java-native-interface shutdown-hook java-11

我有一些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 ++代码并最终退出。有没有人知道从JNI调用启动时允许shutdownHandler方法加入主线程的方法?

1 个答案:

答案 0 :(得分:4)

在第一个示例中,主线程退出,然后JVM检测到没有剩余的非Daemon线程,并将启动JVM关闭。此时,加入主线程没有问题,因为它甚至在关机之前就已结束。

在第二个变体中,主线程(即通过main执行env -> CallStaticVoidMethod(…)方法的线程)正在忙于执行jvm -> DestroyJavaVM()。由于该函数等待关闭处理程序的完成,而您的关闭处理程序等待此线程的完成,因此您将出现死锁。

您也可以使用纯Java代码获得类似的行为。当将System.exit(0);放在main方法的末尾,让主线程启动关闭并等待其完成时,您会遇到类似的死锁。

通常,您不应在关机处理程序中执行join操作。这些处理程序应该清理并尽快返回。

或者,如the documentation所述:

  

Shutdown挂钩在虚拟机的生命周期中的某个微妙的时间运行,因此应进行防御性编码。尤其应将其编写为线程安全的,并尽可能避免死锁。他们也不应盲目依赖可能已经注册了自己的关闭钩子的服务,因此自己可能正在关闭过程中。尝试使用其他基于线程的服务(例如AWT事件调度线程)可能会导致死锁。