4

I have a simple class in Java:

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

And in C++ I do the following JNI Call:

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);
}

If a single program calls the static method c_call_function() it is ok.

But if a multithread program calls the c_call_function(), it gives me the following message, when passes the line env->CallStaticIntMethod(clazz, mid_dummyTest);:

Access violation at 0x000000006FC77154 read to 0x0000000000000000

If the program is multithread, it uses the same JNIEnv variable. But I also tried to load the same JNIEnv via AttachCurrentThread method and it gives me the same problem.

What is the restriction when calling the method bellow by multiple threads as long as I don't create any local references or even delete or modify anything?

    env->CallStaticIntMethod(clazz, mid_dummyTest);
felipe
  • 1,212
  • 1
  • 15
  • 27
  • It sounds like you're saving a reference to the `JNIEnv` and sharing it to another thread, which isn't good. http://stackoverflow.com/a/10082824/2891664 It's been too long since I've worked with JNI to say if that is what actually causes the error. Also, access violation at `0x0` is basically a null pointer exception. It would be nice if you could locate the line that causes it. – Radiodef Apr 28 '17 at 14:58
  • when call env->CallStaticIntMethod(clazz, mid_dummyTest) it crashes. If I call different java methods via JNI it works, but if I call the same java method it crashes. – felipe Apr 28 '17 at 16:36

2 Answers2

3

env should be obtained separately for each thread. You should use AttachCurrentThread() if and only if a thread was *not started by JVM. For each thread that issues AttachCurrentThread(), you must call DetachCurrentThread().

clazz should be obtained separately for each thread, or you can save a global reference to the result received from FindClass().

mid_dummyTest can be saved in a global variable: this id is independent from the thread, env and clazz.

Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
1

I a able to run similar code (look below) where I have multiple threads accessing the same JVM (macOS). I am using pthread.

Few things that are important

  • attaching thread to JVM ("The JNI interface pointer (JNIEnv) is valid only in the current thread. Should another thread need to access the Java VM, it must first call AttachCurrentThread() to attach itself to the VM and obtain a JNI interface pointer.")
  • joining threads
  • i guess it's a good idea to use mutex as well to prevent multiple threads being attached

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

Once you run the code, you get following result:

./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
Oo.oO
  • 12,464
  • 3
  • 23
  • 45
  • 1
    Perfect. Where did you find the docs: "only one thread can use use JVM at a time"? – felipe May 02 '17 at 17:00
  • For better documented and more self containing code take a look here: https://github.com/mkowsiak/jnicookbook/tree/master/recipeNo027 – Oo.oO May 02 '17 at 18:27
  • Can you specify in which section there is written that "only one thread can use JVM at a time"? – Federico Dec 15 '17 at 13:55
  • "Attaching to the VM": https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html – Oo.oO Dec 15 '17 at 15:30
  • Actually there is no such restriction as "only one thread can use JVM at a time". – Vladimir Vaschenko Dec 30 '19 at 07:49
  • > i guess it's a good idea to use mutex as well to prevent multiple threads being attached Wrong. There is no restriction on multiple threads attaching the JVM. What is true is that it is not a good idea to load the JVM from an arbitrary thread. It is best to have a known thread act as the "main-thread" loading the JVM (since Java 1.2 other threads can unload the JVM - with some restrictions). – Dror Harari Feb 27 '23 at 18:15