Android JNI - 调用AttachCurrentThread时不需要调用DetachCurrentThread

22
我一直在阅读关于JNI的内容,但似乎无法弄清楚如果一个线程开始 -> 调用AttachCurrentThread() -> 进行一些JNI调用 -> 线程退出会发生什么。
理想情况下,我们应该在线程退出之前调用DetachCurrentThread(),但是,如果应用程序没有这样做,会产生什么影响?会导致内存泄漏或其他问题吗?

请注意,仅在您附加了线程时才调用 DetachCurrentThread()。如果当前线程是 JVM 拥有的线程,则分离该线程的行为是未定义的。 - technomage
1个回答

34

不调用DetachCurrentThread()肯定会导致内存泄漏;其他后果是基于JVM的,对于Android应用程序来说可能不相关,因为当进程退出时,JVM会关闭。有很多C++包装器可以帮助管理线程Attach / Detach,例如: http://w01fe.com/blog/2009/05/c-callbacks-into-java-via-jni-made-easyier

更新:感谢fadden提供了令人瞪目的链接; 在Dalvik上,没有调用DetachCurrentThread()退出的线程会导致整个虚拟机和进程崩溃。

这里是官方模拟器的logcat,我的代码基于NDK中的HelloJni示例:

10-26 04:16:25.853: D/dalvikvm(1554): Trying to load lib /data/app-lib/com.example.hellojni-2/libhello-jni.so 0xb3d264f0
10-26 04:16:25.893: D/dalvikvm(1554): Added shared lib /data/app-lib/com.example.hellojni-2/libhello-jni.so 0xb3d264f0
10-26 04:16:25.893: D/dalvikvm(1554): No JNI_OnLoad found in /data/app-lib/com.example.hellojni-2/libhello-jni.so 0xb3d264f0, skipping init
10-26 04:16:26.463: D/gralloc_goldfish(1554): Emulator without GPU emulation detected.
10-26 04:16:31.033: D/threadFunction(1554): Attaching
10-26 04:16:31.173: D/threadFunction(1554): Not Detaching
10-26 04:16:31.183: D/dalvikvm(1554): threadid=11: thread exiting, not yet detached (count=0)
10-26 04:16:31.193: D/dalvikvm(1554): threadid=11: thread exiting, not yet detached (count=1)
10-26 04:16:31.193: E/dalvikvm(1554): threadid=11: native thread exited without detaching
10-26 04:16:31.193: E/dalvikvm(1554): VM aborting
10-26 04:16:31.213: A/libc(1554): Fatal signal 6 (SIGABRT) at 0x00000612 (code=-6), thread 1567 (xample.hellojni)

这是添加到hello-jni.c的相关函数:

static JavaVM* jvm = 0;
static jobject activity = 0; // GlobalRef

void* threadFunction(void* irrelevant)
{
    JNIEnv* env;
    usleep(5000000);

    __android_log_print(ANDROID_LOG_DEBUG, "threadFunction", "Attaching");

    (*jvm)->AttachCurrentThread(jvm, &env, NULL);

    jclass clazz = (*env)->GetObjectClass(env, activity);
    jmethodID methodID = (*env)->GetMethodID(env, clazz, "finish", "()V" );
    (*env)->CallVoidMethod(env, activity, methodID);

    __android_log_print(ANDROID_LOG_DEBUG, "threadFunction", "Not Detaching");
//    (*jvm)->DetachCurrentThread(jvm);
}

jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    (*env)->GetJavaVM(env, &jvm);
    activity = (*env)->NewGlobalRef(env, thiz);

    pthread_t hThread;
    pthread_create(&hThread, NULL, &threadFunction, NULL);
    return (*env)->NewStringUTF(env, "Hello from JNI !");
}

这种策略的良好实现可以在WebRTC git存储库中找到。


1
我无法访问该链接。 - pree
另外,如果我创建了大量线程并调用attachcurrentthread而没有detach,则可能会出现内存泄漏。但是,如果有一些长时间运行的线程并且它们在退出时没有调用detachcurrentthread,会发生什么?会有什么影响吗?它不会在退出后自动分离吗? - pree
6
如果一个线程在连接后未能分离并停滞不前,Dalvik虚拟机将会中止,以确保资源泄漏不被忽视。请查看threadExitCheck(),位于https://android.googlesource.com/platform/dalvik/+/kitkat-release/vm/Thread.cpp的第1035行。 - fadden
1
@fadden:非常感谢!我刚刚尝试了一下,当我删除了一个无辜的DetachCurrentThread()调用时,应用程序崩溃了。请查看更新。 - Alex Cohn
1
一个注释:从JNI源代码中可以看出,attach/detach不是递归的。因此,您应该首先调用GetEnv(),仅在失败时调用AttachCurrentThread()/DetachCurrentThread()。否则,您将处于这样的情况:在已经存在有效JNIEnv的范围内调用了一个函数,但您却将其分离,现在任何随后跟随的函数都会出问题。(如果我漏掉了什么,请有人纠正我。) - Edward Falk
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接