C++多线程Java JNI方法调用

4

我有一个简单的Java类:

public class MyClass 
{
    public static void dummyTest() 
    {
    }
}

在C++中,我进行以下JNI调用:

void c_call_function() 
{
    JNIEnv *env ...// the JNIEnv initialization in JNI...
    jclass clazz ...// the class initialization in JNI...

    jmethodID mid_dummyTest = env->GetStaticMethodID(clazz, "dummyTest", "()V");
    env->CallStaticIntMethod(clazz, mid_dummyTest);
}

如果一个单独的程序调用静态方法c_call_function(),那就没问题了。
但是如果一个多线程程序调用c_call_function(),当通过env->CallStaticIntMethod(clazz, mid_dummyTest)这一行时,会给出以下消息:
访问地址0x000000006FC77154读取到0x0000000000000000
如果程序是多线程的,则使用相同的JNIEnv变量。 但我也尝试通过AttachCurrentThread方法加载相同的JNIEnv,它仍然给我带来了同样的问题。
只要我不创建任何本地引用或删除或修改任何内容,调用下面的方法时有什么限制?
    env->CallStaticIntMethod(clazz, mid_dummyTest);

听起来你正在保存对 JNIEnv 的引用并将其共享到另一个线程,这不好。http://stackoverflow.com/a/10082824/2891664 自从我使用 JNI 工作以来已经太长时间了,无法确定是否实际上导致了错误。此外,在 0x0 处的访问冲突基本上是空指针异常。如果您能找到导致它的行,那就太好了。 - Radiodef
当调用 env->CallStaticIntMethod(clazz, mid_dummyTest) 时,它会崩溃。如果我通过 JNI 调用不同的 Java 方法,它可以工作,但如果我调用相同的 Java 方法,它就会崩溃。 - felipe
2个回答

3

env 应该为每个线程单独获取。如果一个线程不是由JVM启动的,则只能使用 AttachCurrentThread() 来获取。对于每个调用 AttachCurrentThread() 的线程,都必须调用 DetachCurrentThread()

clazz 应该为每个线程单独获取,或者您可以将从 FindClass() 获取的结果保存为全局引用。

mid_dummyTest 可以保存在全局变量中:这个ID与线程、envclazz无关。


1
我能够运行类似的代码(请查看下方),其中我有多个线程访问同一个JVM(macOS)。我正在使用pthread。
以下是几个重要的事项:
- 将线程附加到JVM(“JNI接口指针(JNIEnv)仅在当前线程中有效。如果另一个线程需要访问Java VM,则必须首先调用AttachCurrentThread()将其附加到VM并获取JNI接口指针。”) - 加入线程 - 我认为最好还是使用互斥锁来防止多个线程被附加

main.c

#include <stdio.h>
#include <jni.h>
#include <pthread.h>

#define NUM_THREADS 6

pthread_mutex_t mutexjvm;
pthread_t threads[NUM_THREADS];

struct JVM {
  JNIEnv *env;
  JavaVM *jvm;
};

void invoke_class(JNIEnv* env);

void *jvmThreads(void* myJvm) {

  struct JVM *myJvmPtr = (struct JVM*) myJvm;
  JavaVM *jvmPtr = myJvmPtr -> jvm;
  JNIEnv *env = myJvmPtr -> env;

  pthread_mutex_lock (&mutexjvm);
  printf("I will call JVM\n");
  (*jvmPtr)->AttachCurrentThread(jvmPtr, (void**) &(env), NULL);
  invoke_class( env );
  (*jvmPtr)->DetachCurrentThread(jvmPtr);
  pthread_mutex_unlock (&mutexjvm);
  pthread_exit(NULL);
}

JNIEnv* create_vm(struct JVM *jvm)
{
    JNIEnv* env;
    JavaVMInitArgs vm_args;
    JavaVMOption options;
    options.optionString = "-Djava.class.path=./";

    vm_args.options = &options;
    vm_args.ignoreUnrecognized = 0;
    vm_args.version = JNI_VERSION_1_6;
    vm_args.nOptions = 1;

    int status = JNI_CreateJavaVM(&jvm->jvm, (void**)&env, &vm_args);
    if (status < 0 || !env)
        printf("Error\n");
    return env;
}

void invoke_class(JNIEnv* env)
{
    jclass Main_class;
    jmethodID fun_id;
    Main_class = (*env)->FindClass(env, "Main");
    fun_id = (*env)->GetStaticMethodID(env, Main_class, "fun", "()I");
    (*env)->CallStaticIntMethod(env, Main_class, fun_id);
}

int main(int argc, char **argv)
{
    struct JVM myJvm;
    myJvm.env = create_vm(&myJvm);

    if(myJvm.env == NULL)
        return 1;

    pthread_mutex_init(&mutexjvm, NULL);
    for(int i=0; i<NUM_THREADS; i++){
        pthread_create(&threads[i], NULL, jvmThreads, (void*) &myJvm);
        pthread_join(threads[i], 0);
    }
    (*myJvm.jvm)->DestroyJavaVM(myJvm.jvm);
}

Main.java

public class Main {
    public static void main(String[] args){
        System.out.println("Hello, world");
    }
    public static int fun() {
        System.out.println("From JVM");
        return 0;
    }
}

Makefile
all: Main.class main

Main.class: Main.java
    javac Main.java

main.o: main.c
    llvm-gcc -c main.c \
    -I${JAVA_HOME}/include \
    -I${JAVA_HOME}/include/darwin/ \

main: main.o
    ld -o main -L${JAVA_HOME}/jre/lib/server/ \
        -ljvm \
    -rpath ${JAVA_HOME}/jre/lib/server \
    -L/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk \
    -demangle -dynamic -arch x86_64 \
    -macosx_version_min 10.12.0 \
    -lSystem \
    -rpath ${JAVA_HOME}/jre/lib/server/ \
    main.o

clean:
    rm -f Main.class main main.o

运行代码后,您将获得以下结果:
./main
I will call JVM
From JVM
I will call JVM
From JVM
I will call JVM
From JVM
I will call JVM
From JVM
I will call JVM
From JVM
I will call JVM
From JVM

1
太好了。你在哪里找到“一次只能有一个线程使用JVM”的文档? - felipe
为了更好地记录和更自包含的代码,请查看此处:https://github.com/mkowsiak/jnicookbook/tree/master/recipeNo027 - Oo.oO
你能指出哪个部分写着“一次只能有一个线程使用JVM”吗? - Federico
"连接到虚拟机": https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html - Oo.oO
实际上,并没有“一次只能有一个线程使用JVM”的限制。 - Vladimir Vaschenko
我猜使用互斥锁来防止多个线程附加是个好主意。不对。多个线程连接JVM没有限制。真正的情况是从任意线程加载JVM不是一个好主意。最好有一个已知的线程作为“主线程”来加载JVM(自Java 1.2以来,其他线程可以卸载JVM-但有一些限制)。 - Dror Harari

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