Why I should not reuse a jclass and/or jmethodID in JNI?
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();
}
}
As others already wrote
- You can store
jmethodID
in a static C++ variable without problems - You can store local
jobject
orjclass
in a static C++ variable after converting them to global objects by callingenv->NewGloablRef()
I just want to add an additional information here:
The major reason for storing jclass in a static C++ variable will be that you think it is a performance issue to call env->FindClass()
each time.
But I measured the speed of FindClass()
with a performance counter with the QueryPerformanceCounter()
API on Windows. And the result was astonishing:
On my computer with a 3,6 GHz CPU the execution of
jcass p_Container = env->FindClass("java/awt/Container");
takes between 0,01 ms and 0,02 ms. That is incredibly fast. I looked into the Java source code and they use a Dictionary where the classes are stored. This seems to be implemented very efficiently.
I tested some more classes and here is the result:
Elapsed 0.002061 ms for java/net/URL
Elapsed 0.044390 ms for java/lang/Boolean
Elapsed 0.019235 ms for java/lang/Character
Elapsed 0.018372 ms for java/lang/Number
Elapsed 0.017931 ms for java/lang/Byte
Elapsed 0.017589 ms for java/lang/Short
Elapsed 0.017371 ms for java/lang/Integer
Elapsed 0.015637 ms for java/lang/Double
Elapsed 0.018173 ms for java/lang/String
Elapsed 0.015895 ms for java/math/BigDecimal
Elapsed 0.016204 ms for java/awt/Rectangle
Elapsed 0.016272 ms for java/awt/Point
Elapsed 0.001817 ms for java/lang/Object
Elapsed 0.016057 ms for java/lang/Class
Elapsed 0.016829 ms for java/net/URLClassLoader
Elapsed 0.017807 ms for java/lang/reflect/Field
Elapsed 0.016658 ms for java/util/Locale
Elapsed 0.015720 ms for java/lang/System
Elapsed 0.014669 ms for javax/swing/JTable
Elapsed 0.017276 ms for javax/swing/JComboBox
Elapsed 0.014777 ms for javax/swing/JList
Elapsed 0.015597 ms for java/awt/Component
Elapsed 0.015223 ms for javax/swing/JComponent
Elapsed 0.017385 ms for java/lang/Throwable
Elapsed 0.015089 ms for java/lang/StackTraceElement
The above values are from the Java event dispatcher thread. If I execute the same code in a native Windows thread that has been created by CreateThread()
by me, it runs even 10 times faster. Why?
So if you do not call FindClass()
very frequently there is absolutely no problem calling it on demand when your JNI function is called instead of creating a global reference and storing it in a static variable.
Another important topic is thread safety. In Java each thread has it's own independent JNIEnv
structure.
- Global
jobject
orjclass
are valid in any Java thread. - Local objects are only valid in one function call in the
JNIEnv
of the calling thread and are garbage collected when JNI code returns to Java.
Now it depends on the threads that you are using: If you register your C++ function with env->RegisterNatives()
and Java code is calling into your JNI functions then you must store all objects, that you want to use later, as global objects otherwise they will get garbage collected.
But if you create your own thread with the CraeteThread()
API (on Windows) and obtain the JNIEnv
structure by calling AttachCurrentThreadAsDaemon()
then completely other rules will apply: As it is your own thread, that never returns control to Java, the garbage collector will never clean up objects that you have created on your thread and you can even store local objects in static C++ variables without problems (but these cannot be accessed from other threads). In this case it is extremely important that you clean up ALL your local instances manually with env->DeleteLocalRef()
otherwise you will have a memory leak. (However, the garbage collector probably frees all local objects when you call DetachCurrentThread()
, which I never have tested.)
I strongly recommend to load ALL local objects into a wrapper class that calls DeleteLocalRef()
in it's destructor. This is a bullet proof way to avoid memory leaks.
Developing JNI code may be very cumbersome and you may get crashes that you don't understand. To find the cause of a crash open a DOS window and start your Java application with the following command:
java -Xcheck:jni -jar MyApplication.jar
then you will see what problems have happened in JNI code. For example:
FATAL ERROR in native method: Bad global or local ref passed to JNI
and you will find the stacktrace in the log file that Java creates in the same folder where you have the JAR file:
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x6e8655d5, pid=4692, tid=4428
#
etc...