【问题标题】:SIGSEGV when calling Java method from native pthread从本机 pthread 调用 Java 方法时的 SIGSEGV
【发布时间】:2015-06-20 12:28:31
【问题描述】:

在一个通过 JNI 使用 C 代码的 Java 项目中,我有一段本地 C 代码,它获取对一个对象及其方法之一的引用,然后启动一个本地线程,将这些引用传递给一个结构体。当线程尝试调用该方法时,代码会因 SIGSEGV 而崩溃。从主线程调用相同的方法有效。

做了一些研究,我了解到env 引用仅在线程内有效,并且必须首先附加任何其他本机线程。我这样做了,但代码在第一次调用该方法时仍然崩溃。

奇怪的是,当我在创建另一个线程之前从主线程调用相同的方法时(只是取消注释主代码中的行),事情会工作一段时间。输出线程在崩溃之前循环了大约 10,000 次。

方法调用是DataOutputStream.writeShort()。有问题的线程是唯一写给DataOutputStream 的线程。但是,DataOutputStream 连接到 DataInputStream

简化的原生代码:

static void write_output(struct output_state *s) {
    int i;
    jint sample;
    for (i = 0; i < 2 * s->result_len; i += 2) {
        sample = (s->result[i] << 8) + s->result[i+1];
        (*(s->env))->CallVoidMethod(s->env, s->tunerOut, s->writeShort, sample);
    }
}

static void *output_thread_fn(void *arg)
{
    struct output_state *s = arg;
    (*(s->jvm))->AttachCurrentThread(s->jvm, &(s->env), NULL);
    while (!do_exit) {
        // use timedwait and pad out under runs
        safe_cond_wait(&s->ready, &s->ready_m);
        pthread_rwlock_rdlock(&s->rw); //sync access to s with producer thread
        write_output(s);
        pthread_rwlock_unlock(&s->rw);
    }
    (*(s->jvm))->DetachCurrentThread(s->jvm);
    return 0;
}

JNIEXPORT jboolean JNICALL Java_eu_jacquet80_rds_input_SdrGroupReader_open
  (JNIEnv *env, jobject self) {
    jclass clsSelf = (*env)->GetObjectClass(env, self);
    jfieldID fTunerOut = (*env)->GetFieldID(env, clsSelf, "tunerOut", "Ljava/io/DataOutputStream;");
    jobject tunerOut = (*env)->GetObjectField(env, self, fTunerOut);
    jclass cls = (*env)->GetObjectClass(env, tunerOut);
    jmethodID writeShortID = (*env)->GetMethodID(env, cls, "writeShort", "(I)V");

    if (!writeShortID || !cls)
        return 0;

    (*env)->GetJavaVM(env, &(output.jvm));
    output.tunerOut = tunerOut;
    output.writeShort = writeShortID;

    // (*env)->CallVoidMethod(env, tunerOut, writeShortID, 0); // just for testing

    pthread_create(&controller.thread, NULL, controller_thread_fn, (void *)(&controller));
    usleep(100000);
    pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output));
    pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod));
    pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle));

    return 1;
}

jclassjfieldIDjobjectjmethodID 等 JNI 引用是否受到与 JNIEnv 相同的限制,即仅在同一线程内有效?

怀疑这一点,在调用AttachCurrentThread() 之后,我将JNI 参考资料从open() 移至output_thread()。但是,我仍然需要跨线程边界传递 jobject 引用 (self),并且对 GetObjectClass() 的调用会崩溃。

创建线程本机代码并让该线程调用给定类实例的特定方法的正确方法是什么?

【问题讨论】:

    标签: java c multithreading java-native-interface pthreads


    【解决方案1】:

    事实证明我的怀疑是正确的:jobjectjclass 引用确实是本地的,即仅在同一线程内有效,并且仅在当前本地方法返回之前有效。见http://developer.android.com/training/articles/perf-jni.html#local_and_global_references

    我将引用相关代码移动到线程函数的方法是正确的,只是我需要先通过调用NewGlobalRef()self转换为全局引用。

    更新代码:

    static void write_output(struct output_state *s) {
        int i;
        jint sample;
        for (i = 0; i < 2 * s->result_len; i += 2) {
            sample = (s->result[i] << 8) + s->result[i+1];
            (*(s->env))->CallVoidMethod(s->env, s->tunerOut, s->writeShort, sample);
        }
    }
    
    static void *output_thread_fn(void *arg)
    {
        struct output_state *s = arg;
        (*(s->jvm))->AttachCurrentThread(s->jvm, &(s->env), NULL);
        jclass clsSelf = (*(s->env))->GetObjectClass(s->env, s->self);
        jfieldID fTunerOut = (*(s->env))->GetFieldID(s->env, clsSelf, "tunerOut", "Ljava/io/DataOutputStream;");
        s->tunerOut = (*(s->env))->GetObjectField(s->env, s->self, fTunerOut);
        jclass cls = (*(s->env))->GetObjectClass(s->env, s->tunerOut);
        s->writeShort = (*(s->env))->GetMethodID(s->env, cls, "writeShort", "(I)V");
        while (!do_exit) {
            // use timedwait and pad out under runs
            safe_cond_wait(&s->ready, &s->ready_m);
            pthread_rwlock_rdlock(&s->rw); //sync access to s with producer thread
            write_output(s);
            pthread_rwlock_unlock(&s->rw);
        }
        (*(s->jvm))->DetachCurrentThread(s->jvm);
        return 0;
    }
    
    JNIEXPORT jboolean JNICALL Java_eu_jacquet80_rds_input_SdrGroupReader_open
      (JNIEnv *env, jobject self) {
        jclass clsSelf = (*env)->GetObjectClass(env, self);
    
        if (!writeShortID || !cls)
            return 0;
    
        output.self = (*env)->NewGlobalRef(env, self);
        (*env)->GetJavaVM(env, &(output.jvm));
    
        (*env)->CallVoidMethod(env, tunerOut, writeShortID, 0); // just for testing
    
        pthread_create(&controller.thread, NULL, controller_thread_fn, (void *)(&controller));
        usleep(100000);
        pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output));
        pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod));
        pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle));
    
        return 1;
    }
    

    仍然缺少的一件事是在输出线程完成时调用DeleteGlobalRef()。这是为了确保全局引用在不再需要时被释放,以便垃圾收集器可以拾取它。

    【讨论】:

      猜你喜欢
      • 2012-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-02
      相关资源
      最近更新 更多