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(); } }
As others already wrote
jmethodID
in a static C++ variable without problemsjobject
or jclass
in a static C++ variable after converting them to global objects by calling env->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.
jobject
or jclass
are valid in any Java thread. 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...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With