在JNI中,如何根据IBM的性能建议缓存类,methodID和fieldID?

我在IBM上读到了这一点

要访问Java对象的字段并调用它们的方法,本机代码必须调用FindClass(),GetFieldID(),GetMethodId()和GetStaticMethodID()。 对于GetFieldID(),GetMethodID()和GetStaticMethodID(),为给定类返回的ID在JVM进程的生命周期内不会更改。 但是获取字段或方法的调用可能需要在JVM中进行大量工作,因为字段和方法可能是从超类inheritance的,这使得JVM在类层次结构中向上移动以找到它们。 因为给定类的ID是相同的,所以您应该查找它们一次然后重复使用它们。 同样,查找类对象可能很昂贵,因此它们也应该被缓存。

如何在JNI中缓存 methodIDfieldIDclass对象? 是否有必须遵循的内置方法或特定程序?

没有内置的方法可供遵循,但是这里有一个标准的,干净的,可重复的实现,显示了我如何实践IBM的建议。

我将假设您从Java调用DLL并且在整个应用程序生命周期中多次引用它。

示例Native Java Class名为org.stackoverflow.jni.NativeClazz ,它将实现2个内置JNI方法JNI_OnLoad()JNI_OnUnload()

void JNI_OnLoad(JavaVM * vm,void * reserved):此方法将用于将类ID注册为全局变量,并将方法ID和字段ID分配给静态变量。 当Java VM加载驱动程序时,将自动调用该方法; 它只在驾驶员生命周期中被调用一次。

void JNI_OnUnload(JavaVM * vm,void * reserved):此方法将用于释放由JNI_OnLoad()注册的任何全局变量。 VM将在应用程序关闭之前立即自动调用JNI_OnUnload()

基本原理:我的理解是必须将类ID注册为全局引用,以维护任何关联的方法ID /字段ID的可行性。 如果未执行此操作并且从JVM卸载类,则在类重新加载时,方法ID /字段ID可能不同。 如果将类ID注册为全局引用,则不需要将关联的方法ID和字段ID注册为全局引用。 将类ID注册为全局引用可防止关联的Java类卸载,从而稳定方法ID /字段ID值。 应在JNI_OnUnload()删除全局引用,包括类ID。

方法ID和字段ID不由本机代码管理; 它们由虚拟机管理,并且在卸载关联类之前一直有效。 在虚拟机卸载定义类之前,无法显式删除字段ID和方法ID; 它们可以留给VM卸载后处理。

示例代码

以下C ++代码部分中的注释解释了全局注册变量。

这是表示数据对象的Java类BeanObject

 package org.stackoverflow.data; public class BeanObject { String foo = ""; public String getFoo() { return foo; } } 

这是一个骨架Java类NativeClazz

 package org.stackoverflow.jni; import org.stackoverflow.data.BeanObject; public class NativeClazz { // Static area for forced initialization static { // Load Native Library (C++); calls JNI_OnLoad() System.loadLibrary("Native_Library_File_Name"); } /** * A static native method you plan to call. */ public static native void staticNativeMethod(BeanObject bean); /** * A non-static native method you plan to call, to show this also works with * instantiated Java classes. */ public native void instanceNativeMethod(BeanObject bean); } 

这是在NativeClazz上使用javah生成的C ++头文件:

 /* DO NOT EDIT THIS FILE - it is machine generated */ #include  /* Header for class org_stackoverflow_jni_NativeClazz */ #ifndef _Included_org_stackoverflow_jni_NativeClazz #define _Included_org_stackoverflow_jni_NativeClazz #ifdef __cplusplus extern "C" { #endif /* * Class: org_stackoverflow_jni_NativeClazz_staticNativeMethod * Method: staticNativeMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_org_stackoverflow_jni_NativeClazz_staticNativeMethod (JNIEnv *, jclass, jobject); /* * Class: org_stackoverflow_jni_NativeClazz_instanceNativeMethod * Method: instanceNativeMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_org_stackoverflow_jni_NativeClazz_instanceNativeMethod (JNIEnv *, jobject, jobject); #ifdef __cplusplus } #endif #endif 

这是实现头文件的C ++ .cpp文件:

 #include "org_stackoverflow_jni_NativeClazz.h" using namespace std; /************************************************************** * Static Global Variables to cache Java Class and Method IDs **************************************************************/ static jclass JC_BeanObject; static jmethodID JMID_BeanObject_getFoo; /************************************************************** * Declare JNI_VERSION for use in JNI_Onload/JNI_OnUnLoad * Change value if a Java upgrade requires it (prior: JNI_VERSION_1_6) **************************************************************/ static jint JNI_VERSION = JNI_VERSION_1_8; /************************************************************** * Initialize the static Class and Method Id variables **************************************************************/ jint JNI_OnLoad(JavaVM* vm, void* reserved) { // Obtain the JNIEnv from the VM and confirm JNI_VERSION JNIEnv* env; if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) { return JNI_ERR; } // Temporary local reference holder jclass tempLocalClassRef; // STEP 1/3 : Load the class id tempLocalClassRef = env->FindClass("org/stackoverflow/data/BeanObject"); // STEP 2/3 : Assign the ClassId as a Global Reference JC_BeanObject = (jclass) env->NewGlobalRef(tempLocalClassRef); // STEP 3/3 : Delete the no longer needed local reference env->DeleteLocalRef(tempLocalClassRef); // Load the method id JMID_BeanObject_getFoo = env->GetMethodID(JC_BeanObject, "getFoo", "(Ljava/lang/String;)V"); // ... repeat prior line for any other methods of BeanObject // ... repeat STEPS 1-3 for any other classes; re-use tempLocalClassRef. // Return the JNI Version as required by method return JNI_VERSION; } /************************************************************** * Destroy the global static Class Id variables **************************************************************/ void JNI_OnUnload(JavaVM *vm, void *reserved) { // Obtain the JNIEnv from the VM // NOTE: some re-do the JNI Version check here, but I find that redundant JNIEnv* env; vm->GetEnv(reinterpret_cast(&env), JNI_VERSION); // Destroy the global references env->DeleteGlobalRef(JC_BeanObject); // ... repeat for any other global references } /************************************************************** * A Static Native Method **************************************************************/ JNIEXPORT void JNICALL Java_org_stackoverflow_jni_NativeClazz_staticNativeMethod (JNIEnv * env, jclass clazz, jobject jBeanObject) { // Retrieve jstring from the Java Object jstring jFoo = (jstring)env->CallObjectMethod(jBeanObject, JMID_BeanObject_getFoo); // Make accessible to C++ const char * cFoo = env->GetStringUTFChars(jFoo, NULL); // Do something with cFoo... // Release Resources env->ReleaseStringUTFChars(jFoo, cFoo); env->DeleteLocalRef(jFoo); } /************************************************************** * Instance / Non-Static Native Method **************************************************************/ JNIEXPORT void JNICALL Java_org_stackoverflow_jni_NativeClazz_instanceNativeMethod (JNIEnv * env, jobject selfReference, jobject jBeanObject) { // Retrieve jstring from the Java Object jstring jFoo = (jstring)env->CallObjectMethod(jBeanObject, JMID_BeanObject_getFoo); // Make accessible to C++ const char * cFoo = env->GetStringUTFChars(jFoo, NULL); // Do something with cFoo... // Release Resources env->ReleaseStringUTFChars(jFoo, cFoo); env->DeleteLocalRef(jFoo); } 

以下是我如何实践IBM的建议。 考虑像这样的demo java类:

 public class SimpleClazz { public int value = 10; public native int getValue(); static { // Load Native Library System.loadLibrary("The native library name"); } } 

相应的jni头文件是这样的:

 /* DO NOT EDIT THIS FILE - it is machine generated */ #include  /* Header for class SimpleClazz */ #ifndef _Included_SimpleClazz #define _Included_SimpleClazz #ifdef __cplusplus extern "C" { #endif /* * Class: SimpleClazz * Method: getValue * Signature: ()I */ JNIEXPORT jint JNICALL Java_SimpleClazz_getValue (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif 

根据IBM的建议,我们需要缓存使用的类SimpleClazz和对象成员value的字段id。

在学好这篇好文章后 ,我将SimpleClazz缓存在函数JNI_OnLoad ,该函数在加载本机库时调用(例如,通过System.loadLibrary)。 在JNI_Onload ,我们找到了类并将此jclass存储为全局字段。

此外,在getValue的本机实现中,我们使用静态局部变量来缓存value的字段id。 这种设计是为了确保这个字段id可以在更好的范围内,而不是在全局范围内。 这种设计的缺点是每次调用此函数时都需要与NULL进行比较。 我从“Java Native Interface:Programmer’s Guide and Specification ”一书的第4.4.1节中学到了这个设计。

最后,我们还需要编写函数JNI_OnUnload ,当包含本机库的类加载器被垃圾收集时调用该函数。 在这个函数中,我们发布了jclass的全局引用。

我的cpp实现如下所示:

 #include  #include  static jclass simpleCls; // According to http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#JNI_OnLoad // The VM calls JNI_OnLoad when the native library is loaded (for example, through System.loadLibrary). jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } else { jclass localSimpleCls = (*env)->FindClass("SimpleClazz"); if (localSimpleCls == NULL) { return JNI_ERR; } simpleCls = (jclass) (*env)->NewGlobalRef(env, localSimpleCls); } return JNI_VERSION_1_6; } JNIEXPORT jint JNICALL Java_SimpleClazz_getValue(JNIEnv * env, jobject thiz){ static jfieldID valueID = NULL; if (valueID == NULL) { valueID = (*env)->GetFieldID(env, simpleCls, "value", "I"); if (valueID == NULL){ return JNI_ERR; // Exception thrown } } jint value = (*env)->GetIntField(env, thiz, valueID); return value; } // According to http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#JNI_OnUnload // The VM calls JNI_OnUnload when the class loader containing the native library is garbage collected. void JNI_OnUnload(JavaVM *vm, void *reserved) { JNIEnv* env; if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) { // Something is wrong but nothing we can do about this :( return; } else { if (0 != NULL){ (*env)->DeleteGlobalRef(env, simpleCls); } } } 

你可以有一些像这样的实用程序结构:

 typedef struct MYVARIANT_FID_CACHE { int cached; jclass clazz; jfieldID pAddress; } MYVARIANT_FID_CACHE; MYVARIANT_FID_CACHE VARIANTFc; void cacheMYVARIANTFields(JNIEnv *env, jobject lpObject) { if (VARIANTFc.cached) return; VARIANTFc.clazz = env->GetObjectClass(lpObject); VARIANTFc.pAddress = env->GetFieldID(VARIANTFc.clazz, "pAddress", "I"); VARIANTFc.cached = 1; } VARIANT *getMYVARIANTFields(JNIEnv *env, jobject lpObject, VARIANT *lpStruct) { if (!VARIANTFc.cached) cacheVARIANT2Fields(env, lpObject); lpStruct = (VARIANT*)(env->GetIntField(lpObject, VARIANTFc.pAddress)); return lpStruct; } 

这取自我的问题: https : //stackoverflow.com/questions/10617714/how-to-extend-swt-com-support

有关一些好例子,请查看os_structs.c它与eclipse SWT实现捆绑在一起。

注意:上面的代码只是一个示例,可以适用于不同的操作系统。 它还只显示“如何访问java字段”; 对于方法,您可以遵循相同的方法。

对于简单的情况,我在C代码中使用静态字段,使用从我的java类调用的initId方法初始化:

 package demo; public class WithNatives { static { initIDs(); } private static native void initIDs(); } 

而在C:

 static jmethodID methodId; void JNICALL Java_demo_WithNatives_initIDs(JNIEnv *env, jclass clazz) { // initialize methodId }