8

I'm writing a C++ app with native threads (pthreads) and I need to call some Java methods etc. I'm not sure which JNI objects can be safely cached ie stored in my C++ object for use later, possibly/probably by a different thread. I do know that if my class' methods can be called by different threads I mustn't cache the JNIEnv, but instead cache the JavaVM and get a JNIEnv by attaching the current thread. But does that also mean I can't cache anything obtained from a JNIEnv? I need to use the objects obtained by the following JNIEnv methods:

FindClass, GetMethodID, NewObject, NewGlobalRef

Do those stay valid across threads, or do I have to get new ones every time? If the latter, is there a way to create an object in one native thread and be able to access the same object in a different thread?

realh
  • 962
  • 2
  • 7
  • 22

2 Answers2

10

JNI methods like FindClass, GetMethodID, GetFieldID are expensive operation that are guaranteed to generate the same result over the life of the JVM. Since these operations are time consuming, it is wise to store the result somewhere to be reused later on in the native side (this is caching).

JNI caching regards only these JNI function calls. If you want to cache any other C++ or Java object this is a different topic. (Just to be clear).

The cached classes, methods and fields do not depend on the thread they are retrieved from, so they are valid across different threads. At most you have to perform thread safe operations when getting or setting some object's field with Set<type>Field or Get<type>Field.

Since FindClass returns a local reference to the class object, you have to turn it into a global reference to guarantee its reuse after the function that retrieves it ends. You can achieve this by using NewGlobalReference:

jclass tmp_double_Class = env->FindClass( "java/lang/Double" ); // Check for exceptions!
double_Class = static_cast<jclass>( env->NewGlobalRef( tmp_double_Class ) );
if( double_Class == NULL )
  return;
env->DeleteLocalRef( tmp_double_Class );

Here you have an example of the all JNI Caching topic:

MyJni.cpp:

// Just a shortcut for checking for exceptions
#define CHECK_JNI_EXCEPTION( JNIenv ) \
  if( JNIenv->ExceptionCheck() )\
  {\
    JNIenv->ExceptionClear();\
    return JNI_FALSE;\
  }\
\

// Global variables
jclass    point_Class;
jmethodID point_ctor_Method;
jfieldID  point_x_Field;
jfieldID  point_y_Field;

JNIEXPORT jboolean JNICALL Java_com_company_package_MyClass_nativeInit( JNIEnv * env,
                                                                        jclass   clazz )
{
  // Cache java.lang.Double class, methods and fields
  jclass tmp_point_Class = env->FindClass( "android/graphics/Point" );
  CHECK_JNI_EXCEPTION( env )
  point_Class = static_cast<jclass>( env->NewGlobalRef( tmp_point_Class ) );
  if( point_Class == NULL )
    return JNI_FALSE;
  env->DeleteLocalRef( tmp_point_Class );
  point_ctor_Method = env->GetMethodID( point_Class, "<init>", "(II)V" );
  CHECK_JNI_EXCEPTION( env )
  point_x_Field = env->GetFieldID( point_Class, "x", "I" );
  CHECK_JNI_EXCEPTION( env )
  point_y_Field = env->GetFieldID( point_Class, "y", "I" );
  CHECK_JNI_EXCEPTION( env )
  return JNI_TRUE;
}

MyJni.java:

package com.company.package;

class MyClass {
  // ... All java code here ... 

  // Trigger JNI Caching (could be also done using JNI_OnLoad...)
  private static native void nativeInit();

  static {
    System.loadLibrary( "mylib" );
    nativeInit(); // should check the result
  }
}

Have fun ;)

Sdra
  • 2,297
  • 17
  • 30
4

Objects are not thread-specific. They are initially "local" references, and if you want to keep a copy you have to tell the VM that you're doing so by creating (and, eventually, deleting) a "global" reference.

See http://developer.android.com/training/articles/perf-jni.html, especially the "Local and Global References" section.

fadden
  • 51,356
  • 5
  • 116
  • 166
  • Thanks. That page is more informative about that sort of thing than Oracle's JNI spec. – realh Dec 18 '12 at 16:59
  • Shouldn't the assginment of the global variables be done with a lock held or something? I can barely find any documentation or examples on thread safety in JNI. – Malcolm Mar 14 '13 at 19:09
  • 1
    If they're set up by one thread and read by another, then the values should be protected by a mutex, unless you can guarantee that the reader threads are started after the values are written (and, once written, the values are never updated). Many apps achieve this by loading JNI libs early and doing the setup in `JNI_OnLoad`, before other threads have been started. Deep dive on the subject: http://developer.android.com/training/articles/smp.html – fadden Mar 14 '13 at 20:56