我有一些 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_argsJavaVM *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 回答
慕勒3428872
TA贡献1848条经验 获得超6个赞
在您的第一个示例中,主线程退出,然后 JVM 检测到没有剩余的非守护进程线程并将启动 JVM 关闭。此时,加入主线程没有问题,因为它甚至在关闭之前就已经结束了。
在您的第二个变体中,主线程,即main
通过执行方法的线程env -> CallStaticVoidMethod(…)
正忙于执行jvm -> DestroyJavaVM()
。由于该函数等待关闭处理程序的完成而您的关闭处理程序等待该线程的完成,因此您会遇到死锁。
您也可以使用纯 Java 代码获得类似的行为。当您放置System.exit(0);
在main
方法的末尾,让主线程启动关闭并等待其完成时,您会遇到类似的死锁。
通常,您不应join
在关闭处理程序中执行操作。这些处理程序应该尽快清理并返回。
或者,正如文档所说:
关闭钩子在虚拟机生命周期的一个微妙时刻运行,因此应该进行防御性编码。特别是,它们应该被编写为线程安全的,并尽可能避免死锁。他们也不应该盲目依赖可能已经注册了自己的关闭钩子的服务,因此他们自己可能会在关闭过程中。例如,尝试使用其他基于线程的服务(例如 AWT 事件分派线程)可能会导致死锁。
添加回答
举报
0/150
提交
取消