我有一些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
方法加入主线程的方法?
答案 0 :(得分:4)
在第一个示例中,主线程退出,然后JVM检测到没有剩余的非Daemon线程,并将启动JVM关闭。此时,加入主线程没有问题,因为它甚至在关机之前就已结束。
在第二个变体中,主线程(即通过main
执行env -> CallStaticVoidMethod(…)
方法的线程)正在忙于执行jvm -> DestroyJavaVM()
。由于该函数等待关闭处理程序的完成,而您的关闭处理程序等待此线程的完成,因此您将出现死锁。
您也可以使用纯Java代码获得类似的行为。当将System.exit(0);
放在main
方法的末尾,让主线程启动关闭并等待其完成时,您会遇到类似的死锁。
通常,您不应在关机处理程序中执行join
操作。这些处理程序应该清理并尽快返回。
或者,如the documentation所述:
Shutdown挂钩在虚拟机的生命周期中的某个微妙的时间运行,因此应进行防御性编码。尤其应将其编写为线程安全的,并尽可能避免死锁。他们也不应盲目依赖可能已经注册了自己的关闭钩子的服务,因此自己可能正在关闭过程中。尝试使用其他基于线程的服务(例如AWT事件调度线程)可能会导致死锁。