Native threads and JNI

It is well known a thread not generated from Java itself, like pthread_create on android, or a native activity, can’t find a JVM method by calling Java env’s FindClass. These method calls will return class not found (nullptr), even for prelude classes, like java.lang.String.

Turns out findClass method relies on a ClassLoader to find an specific jclass object. The solution, is simple, but subtle though.

Our native thread is not attached to any VM/Java Env and thus env->FindClass calls will fail. We need another way to find classes. And what class has a FindClass method ? Well, no other class does. But a ClassLoader exposes a loadClass method, which indirectly calls findClass and many other methods. Simply enough, we need to obtain a reference ClassLoader, being the most obvious java code to be: object.getClass().getClassLoader().

In my case, I get such class loader from the call to v8 initialisation. This method is public native void InitializeV8();. In native:

    JNIEXPORT void JNICALL
    Java_com_spellington_task_TaskRunnerInstance_InitializeV8(JNIEnv *env, jobject obj) {

        ...

        // obj if implicit this on java call to the native method.
        auto rc = env->GetObjectClass(obj);

        // obj.getClass()
        jclass clazz = env->GetObjectClass(rc);    

        // find getClassLoader method
        jmethodID getClassLoaderMethod = env->GetMethodID(clazz,
                                                          "getClassLoader",
                                                          "()Ljava/lang/ClassLoader;");

        // obtain class' ClassLoader reference.
        jobject objClassLoader = env->CallObjectMethod(rc, getClassLoaderMethod);     

        // protect this ref from GC ! This is mandatory. 
        //   objClassLoader must be stored somewhere else for later usage
        objClassLoader = env->NewGlobalRef(objClassLoader);

        // ClassLoader class
        jclass objClassLoaderClass = env->FindClass("java/lang/ClassLoader");
        
        // loadClass reference.
        //   Must be stored somewhere else for later usage.
        jmethodID loadClassMethod = env->GetMethodID(
                objClassLoaderClass,
                "loadClass",
                "(Ljava/lang/String;)Ljava/lang/Class;");
    }

This code just got a reference to a valid ClassLoader and a methodID to its loadClass method. Now we have an alternative to just calling findClass:

const char* const className = "com/spellington/HCSurfaceView";

auto clazz = env->FindClass(className);

// if findClass failed, try the ClassLoader alternative. 
// This will be true in two different scenarios:
//    + className, effectively does not exist.
//    + a native thread calls into JNI
if (clazz==nullptr) {
    // findClass alternative based on our ClassLoader
    clazz = static_cast<jclass>(
                env->CallObjectMethod(
                        objClassLoader,
                        loadClassMethod,
                        env->NewStringUTF(className)));
}

// if clazz is nullptr, there another two different scenarios:
//    + most likely, className is not a valid fully qualified class name.
//    + a native thread is trying to find a class which does not exist in the ClassLoader,
//        but might exist on another ClassLoader. This is likely if and only if your app
//        has customs ClassLoader instances. 

When using mixed Java/native thread, or just calling JNI in a multithread environment, you might want to pay attention to Java VM’s AttachCurrentThread method… But that’s another story.

Published by ibon

Chocolate engineer, software eater. Data visualisation at Workday. Past: Platochat, SdkBox, Chukong, Ludei.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: