Java – JNI GetMethodID causes an error in the native thread

JNI GetMethodID causes an error in the native thread… here is a solution to the problem.

JNI GetMethodID causes an error in the native thread

In android, I create a local thread using pthread_create, and then during the callback, call FindClass to get a Java class. But it doesn’t work. I got hints from android jni tips
I FindClass from A solution was found in any thread in Android JNI

I modified it for my project like this
[edit].

JavaVM* gJvm = nullptr;
static jobject gClassLoader;
static jmethodID gFindClassMethod;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
    gJvm = pjvm;   cache the JavaVM pointer
    auto env = getEnv();
    replace with one of your classes in the line below
    auto randomClass = env->FindClass("com/example/RandomClass");
    jclass classClass = env->GetObjectClass(randomClass);
    auto classLoaderClass = env->FindClass("java/lang/ClassLoader");
    auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
                                             "()Ljava/lang/ClassLoader;" );
    gClassLoader = env->CallObjectMethod(randomClass, getClassLoaderMethod);
    gClassLoader = env->NewGlobalRef(gClassLoader);
    gFindClassMethod = env->GetMethodID(classLoaderClass, "loadClass",
                                    "(Ljava/lang/String;)Ljava/lang/Class;" );

check. this is ok
    jclass cls = env->FindClass("com/example/data/DataTest");
    jmethodID methoID = env->GetMethodID(cls, "name", "()Ljava/lang/String;" );
    LOG_INFO("cls is %p\n", cls);

return JNI_VERSION_1_6;
}

JNIEnv* getEnv() {
    JNIEnv *env;
    int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
    if(status < 0) {    
        status = gJvm->AttachCurrentThread(&env, NULL);
        if(status < 0) {        
        return nullptr;
        }
    }  
    return env;
}

jclass findClass(const char* name) {
     JNIEnv *env = getEnv();
     jclass resultClass = 0;
     if(env)
     {
        resultClass = env->FindClass(name);
        it can not found class in native thread, use loadClass method
        if (!resultClass)
        {
            LOG_INFO("can not find the class");
            return value is not null. 
            return static_cast<jclass>(env->CallObjectMethod(gClassLoader, gFindClassMethod, env->NewStringUTF(name))); 
        }
    }
    return resultClass;
}
.......
thread callback 
void *proc(void *)
{
  JNIEnv *env = getEnv();
  jclass cls = findClass("com/example/data/DataTest");
  if (cls)
  {
    LOG_INFO("GetMethodID");
    crash
    jmethodID methodID = env->GetMethodID(cls, "name", "()Ljava/lang/String;" ); 
    LOG_INFO("proc tag is %p\n", tag);
  }
}
.....
pthread_create(&handle, NULL, proc, 0);
.....

The program exits at env->GetMethodID. I’m getting this error:

Invalid indirect reference 0x40d8bb20 in decodeIndirectRef.

If I remove resultClass = env->FindClass(name) from findClass; , that’s it. “Proc tag is” can be printed.

//correct 
jclass findClass(const char* name) {
     JNIEnv *env = getEnv();
     jclass resultClass = 0;
     if(env)
     {
        if (!resultClass)
        {
            return static_cast<jclass>(env->CallObjectMethod(gClassLoader, gFindClassMethod, env->NewStringUTF(name))); 
        }
    }
    return resultClass;
}

in env->FindClass(name); Is there any conflict with env->CallObjectMethod to loadClass?

Is this a bug? What can be done to solve this problem?

Solution

Don’t do this:

gClassLoader = env->CallObjectMethod(randomClass, getClassLoaderMethod);

In particular, never store local references (what CallObjectMethod returns) anywhere other than local variables.

If you want to access the value outside of the function that gets the local reference, you need to use NewGlobalRef to get the global reference. Once execution returns to the VM in that thread, the local reference is invalidated.

See the Local and Global References section documentation in JNI Tips.

Related Problems and Solutions