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:
jclass
and jmethodID
thru different JNI functions? I thought that this values were always the same.jclass
and jmethodID
for each JNI function?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();
}
}