从本机 pthread 调用 Java 方法时的 SIGSEGV
SIGSEGV when calling Java method from native pthread
在一个通过 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;
}
jclass
、jfieldID
、jobject
和 jmethodID
等 JNI 引用是否受到与 JNIEnv
相同的限制,即仅在同一个话题?
出于怀疑,我在调用 AttachCurrentThread()
之后立即将 JNI 参考内容从 open()
移动到 output_thread()
。但是,我仍然需要跨线程边界传递 jobject
引用 (self
),并且对 GetObjectClass()
的调用会崩溃。
创建线程本机代码并让该线程调用给定 class 实例的特定方法的正确方法是什么?
事实证明我的怀疑是正确的:jobject
和 jclass
引用确实是本地的,即仅在同一线程内有效,并且仅在当前本机方法 returns 之前有效。参见 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()
。这是为了确保在不再需要时释放全局引用,以便垃圾收集器可以将其回收。
在一个通过 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;
}
jclass
、jfieldID
、jobject
和 jmethodID
等 JNI 引用是否受到与 JNIEnv
相同的限制,即仅在同一个话题?
出于怀疑,我在调用 AttachCurrentThread()
之后立即将 JNI 参考内容从 open()
移动到 output_thread()
。但是,我仍然需要跨线程边界传递 jobject
引用 (self
),并且对 GetObjectClass()
的调用会崩溃。
创建线程本机代码并让该线程调用给定 class 实例的特定方法的正确方法是什么?
事实证明我的怀疑是正确的:jobject
和 jclass
引用确实是本地的,即仅在同一线程内有效,并且仅在当前本机方法 returns 之前有效。参见 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()
。这是为了确保在不再需要时释放全局引用,以便垃圾收集器可以将其回收。