Why I should not reuse a jclass and/or jmethodID in JNI?

YuppieNetworking picture YuppieNetworking · Jan 19, 2010 · Viewed 21.3k times · Source

This is a question related to a previous post, but this post was solved and now I wanted to change the direction of the question.

When working with JNI, it is necessary to ask the JNIEnv object for jclass and jmethodID for each class and method that will be used in the C/C++ code. Just to be clear, I want to call Java contructors or methods from C/C++.

Since the communication from Java to C/C++ (and viceversa) is costly, I initially thought that one way to minimize this was to reuse the jclass and jmethodID. Therefore, I saved this instances in global variables as follows:

jclass    someClass  = NULL;
jmethodID someMethod = NULL;

JNIEXPORT jobject JNICALL Java_example_method1(JNIEnv *env, jobject jobj) {
    // initialize someClass and someMethod if they are NULL
    // use someClass and someMethod to call java (for example, thru NewObject)
}

JNIEXPORT jobject JNICALL Java_example_method2(JNIEnv *env, jobject jobj) {
    // initialize someClass and someMethod if they are NULL
    // use someClass and someMethod to call java again
}

A more specific (and useful) example, which I use to throw exceptions from anywhere in my JNI functions:

jclass    jniExceptionClass           = NULL;

void throwJavaException(JNIEnv *env, const char* msg) {
    if (!jniExceptionClass) {
        jniExceptionClass = env->FindClass("example/JNIRuntimeException");
    }
    if (jniExceptionClass)
        env->ThrowNew(jniExceptionClass, msg);
    }
}

The problem is that I continued to use this pattern and got a segmentation fault that was only solved by not-reusing this variables (this was the solution to the previous post).

The questions are:

  • Why is it illegal to reuse the jclass and jmethodID thru different JNI functions? I thought that this values were always the same.
  • Just for curiosity: what is the impact/overhead of initializing all necessary jclass and jmethodID for each JNI function?

Answer

bmargulies picture bmargulies · Jan 19, 2010

The rules here are clear. Method ID and field ID values are forever. You can hang onto them. The lookups take some time.

jclass, on the other hand, is generally a local reference. A local reference survives, at most, the duration of a single call to a JNI function.

If you need to optimize, you have to ask the JVM to make a global reference for you. It's not uncommon to acquire and keep references to common classes like java.lang.String.

Holding such a reference to a class will prevent it (the class) from being garbage-collected, of course.

jclass local = env->FindClass(CLS_JAVA_LANG_STRING);
_CHECK_JAVA_EXCEPTION(env);
java_lang_string_class = (jclass)env->NewGlobalRef(local);
_CHECK_JAVA_EXCEPTION(env);
env->DeleteLocalRef(local);
_CHECK_JAVA_EXCEPTION(env);

The check macro calls:

static inline void
check_java_exception(JNIEnv *env, int line)
{
    UNUSED(line);
    if(env->ExceptionOccurred()) {
#ifdef DEBUG
        fprintf(stderr, "Java exception at rlpjni.cpp line %d\n", line);
        env->ExceptionDescribe();
    abort();
#endif
        throw bt_rlpjni_java_is_upset();
    }
}