YuppieNetworking YuppieNetworking - 1 month ago 7
Java Question

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

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

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