JNI线程正确附着/分离问题:调用DetachCurrentThread崩溃
解决JNI线程中
DetachCurrentThread崩溃的问题 首先得明确JNI线程的核心规则:由原生代码(比如std::thread)创建的线程,必须先Attach到Java虚拟机(JavaVM)才能调用JNI函数,用完之后需要Detach,但Detach的时机绝对不能出错——如果在JNI环境还在被使用的时候调用Detach,必然会导致崩溃。
正确的操作步骤
第一步:保存JavaVM全局指针
在JNI初始化阶段(比如JNI_OnLoad),把JavaVM的指针保存下来,因为JNIEnv是线程局部的,不能跨线程使用,但JavaVM是全局唯一的,用来在原生线程中获取当前线程的JNIEnv。第二步:在原生线程入口处Attach线程
当std::thread开始执行时,第一时间用保存的JavaVM指针调用AttachCurrentThread获取JNIEnv,这一步是后续所有JNI操作的前提。第三步:执行回调逻辑,确保所有JNI操作完成
调用注册的Kotlin回调函数,完成所有需要JNI参与的逻辑(比如调用Java方法、访问对象字段等),过程中不要调用Detach。第四步:所有JNI操作结束后,安全Detach线程
只有当线程中所有依赖JNIEnv的代码都执行完毕,并且没有任何后续JNI调用的可能时,再调用DetachCurrentThread。
代码示例
保存JavaVM和回调对象
#include <jni.h> #include <thread> JavaVM* g_global_jvm = nullptr; jobject g_global_callback = nullptr; // JNI初始化时获取JavaVM JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { g_global_jvm = vm; return JNI_VERSION_1_8; } // 注册Kotlin回调的JNI方法 extern "C" JNIEXPORT void JNICALL Java_com_your_package_NativeBridge_CppSetCallback( JNIEnv* env, jobject /* this */, jobject callback) { // 创建全局引用,避免回调对象被GC回收 if (g_global_callback != nullptr) { env->DeleteGlobalRef(g_global_callback); } g_global_callback = env->NewGlobalRef(callback); }
原生线程中正确调用回调并Detach
void native_thread_task() { JNIEnv* env = nullptr; // Attach当前线程到VM,获取JNIEnv jint attach_result = g_global_jvm->AttachCurrentThread(&env, nullptr); if (attach_result != JNI_OK) { // 处理Attach失败的情况,比如打日志 return; } try { // 获取回调接口的方法ID(假设回调是一个带onEvent()方法的接口) jclass callback_clazz = env->GetObjectClass(g_global_callback); jmethodID callback_method = env->GetMethodID(callback_clazz, "onEvent", "()V"); // 调用Kotlin回调函数 env->CallVoidMethod(g_global_callback, callback_method); // 释放局部引用,避免内存泄漏 env->DeleteLocalRef(callback_clazz); } catch (...) { // 捕获并清除JNI异常,防止异常扩散导致崩溃 if (env->ExceptionCheck()) { env->ExceptionClear(); } } // 所有JNI操作完成,安全Detach线程 g_global_jvm->DetachCurrentThread(); } // 启动原生线程的示例函数 extern "C" JNIEXPORT void JNICALL Java_com_your_package_NativeBridge_StartNativeThread( JNIEnv* /* env */, jobject /* this */) { std::thread(native_thread_task).detach(); }
常见崩溃原因
- Detach时机错误:如果在回调执行过程中(比如
CallVoidMethod还没返回)就调用Detach,此时JNIEnv还在被使用,虚拟机尝试回收线程的JNI资源会直接崩溃。 - 未Attach就Detach:如果线程没有先调用
AttachCurrentThread就执行DetachCurrentThread,虚拟机找不到对应的线程关联信息,必然崩溃。 - 回调对象被GC回收:如果没有把回调对象转为全局引用,Java侧可能会回收这个对象,导致调用回调时出现空指针或非法访问错误。
记住一个核心原则:Attach和Detach必须成对出现,且Detach必须在所有JNI操作彻底结束之后执行。
内容的提问来源于stack exchange,提问作者Martin Perry
相关产品推荐
相关产品推荐

