8

我正在使用本机线程(pthreads)编写一个 C++ 应用程序,我需要调用一些 Java 方法等。我不确定哪些 JNI 对象可以安全缓存,即存储在我的 C++ 对象中以供以后使用,可能/可能由不同的线程。我知道如果我的类的方法可以被不同的线程调用,我不能缓存 JNIEnv,而是缓存 JavaVM 并通过附加当前线程来获取 JNIEnv。但这是否也意味着我不能缓存从 JNIEnv 获得的任何东西?我需要使用通过以下 JNIEnv 方法获得的对象:

FindClass、GetMethodID、NewObject、NewGlobalRef

这些是否在线程中保持有效,或者我每次都必须获得新的?如果是后者,有没有办法在一个本机线程中创建一个对象并能够在不同的线程中访问同一个对象?

4

2 回答 2

10

JNI 方法如FindClass, GetMethodID,GetFieldID是昂贵的操作,保证在 JVM 的生命周期内生成相同的结果。由于这些操作非常耗时,因此明智的做法是将结果存储在某个地方以便稍后在本机端重用(这就是缓存)。

JNI 缓存仅考虑这些 JNI 函数调用。如果您想缓存任何其他 C++ 或 Java 对象,这是一个不同的主题。(只是为了清楚)。

缓存的类、方法和字段不依赖于从中检索它们的线程,因此它们在不同的线程中有效。Set<type>Field在使用or获取或设置某些对象的字段时,您最多必须执行线程安全操作Get<type>Field

由于 FindClass 返回对对象的局部引用,因此您必须将其转换为全局引用以保证在检索它的函数结束后重用它。您可以通过使用 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 );

这里有一个关于所有 JNI 缓存主题的示例:

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
  }
}

玩得开心 ;)

于 2013-10-29T11:34:44.683 回答
4

对象不是特定于线程的。它们最初是“本地”引用,如果您想保留副本,则必须通过创建(并最终删除)“全局”引用来告诉 VM 您正在这样做。

请参阅http://developer.android.com/training/articles/perf-jni.html,尤其是“本地和全球参考”部分。

于 2012-12-18T01:09:59.383 回答