开发者

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开发者_如何学Go* 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?


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

  1. You can store jmethodID in a static C++ variable without problems
  2. You can store local jobject 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.

  1. Global jobject or jclass are valid in any Java thread.
  2. 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...


Inside JNI_OnLoad, you need to use NewGlobalRef on the jclass values returned by FindClass before caching them.

Then, inside JNI_OnUnload you call DeleteGlobalRef on them.


As I remember jclass is will be local to the calling method so can't be cached, however the method id can be. See http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/design.html for more detaild info.

Sorry, I don't know about the performance aspect, any time I've used JNI it has been insignificant compared to the task in hand.


You can cache and use your method/function/class IDs and safely use them if you code for it appropriately. I have answered a similar question here on SO and posted explicit code for how to follow IBM's caching performance recommendations.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜