Search code examples
javajava-native-interface

JNI class ID segfault


I have attempted to cache the MethodId and JClass in Java JNI, but I experienced an EXE_BAD_ACCESS when I used the cached values. The error went away when I requested the values inline with the function using them. I figured out that I need to use a global reference, but this hasn't solved the segfault.

Related info to jni caching (slightly outdated signatures) - In JNI, how do I cache the class, methodID, and fieldIDs per IBM's performance recommendations?

Header:

extern jclass java_class_boolean;
extern jmethodID java_method_boolean;

CPP:

jclass java_class_boolean;
jmethodID java_method_boolean;
....
void initStatic(JNIEnv* env){
    java_class_boolean = env->FindClass("java/lang/Boolean");
    if (java_class_boolean){
        env->NewGlobalRef(java_class_boolean);
        java_method_boolean = env->GetMethodID(java_class_boolean, "<init>", "(Z)V");
    }
}

Usage (different CPP class, importing shared header):

    jclass bc = env->FindClass("java/lang/Boolean");
    jmethodID bm = env->GetMethodID(bc, "<init>", "(Z)V");

    std::cout << "\nClass new: ";
    std::cout << bc;
    std::cout << " Class old: ";
    std::cout << java_class_boolean;
    std::cout << "\nMethod new: ";
    std::cout << bm;
    std::cout << " Method old: ";
    std::cout << java_method_boolean;
    std::cout << "\n";
    result2 = env->NewObject(bc, bm, 1);

Result

Class new: 0x7fcce4430110 Class old: 0x7fcce6a23098 Method new: 0x7fcce471c288 Method old: 0x7fcce471c288

But if run with the cached class, it gives a segfault.

V [libjvm.dylib+0x309bcf] alloc_object(_jclass*, Thread*)+0x15

OS is Mac 10.12. JDK 1.8.0_25.

Also that the tests so far are only single threaded and the JNI env is the same.

Printing description of env: (JNIEnv *) env = 0x00007fcc3e0011e8 Printing description of env: (JNIEnv *) env = 0x00007fcc3e0011e8

Both initStatic and the future usage are on "Thread 4" with the same env instance (passed by JNI, not cached). There are other threads, but the segfault and the init are on the same thread.


Solution

  • The issue was that I did not realize that ->NewGlobalRef returned an object.

    I created this function to resolve the issue.

    inline jclass find_class_global(JNIEnv* env, const char *name){
        jclass c = env->FindClass(name);
        jclass c_global = 0;
        if (c){
            c_global = (jclass)env->NewGlobalRef(c);
            env->DeleteLocalRef(c);
        }
        return c_global;
    }