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

Jef*_*f G 4 java java-native-interface multithreading 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);
}
Run Code Online (Sandbox Code Playgroud)

当我从命令行运行此命令时,它可以按预期工作(主线程退出并几乎立即返回到命令提示符)。但是,如果我编写了一个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();
Run Code Online (Sandbox Code Playgroud)

然后,该shutdownHandler方法将挂起,直到30秒超时结束,然后将控制权返回给C ++代码并最终退出。有谁知道shutdownHandler从JNI调用开始时允许方法加入主线程的方法?

Hol*_*ger 6

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

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

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

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

或者,如文档所述

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