获取字段ID和方法ID时,需要用字段、方法的名字和描述符进行一个检索。检索过程相对比较费时,因此本节讨论用缓存技术来减少这个过程带来的消耗。
缓存字段ID和方法ID的方法主要有两种。两种区别主要在于缓存发生的时刻,是在字段ID和方法ID被使用的时候,还是定义字段和方法的类静态初始化的时候。 也就是使用时缓存 还是 类的静态初始化过程中缓存字段和方法ID
使用时缓存
字段ID和方法ID可以在字段的值被访问或者方法被回调的时候缓存起来。下面的代码中把字段ID存储在静态变量当中,这样当本地方法被重复调用时,不必重新搜索字段ID:
JNIEXPORT void JNICALL Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj) { static jfieldID fid_s = NULL; /* cached field ID for s */ jclass cls = (*env)->GetObjectClass(env, obj); jstring jstr; const char *str; if (fid_s == NULL) { fid_s = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;"); if (fid_s == NULL) { return; /* exception already thrown */ } } printf("In C:\n"); jstr = (*env)->GetObjectField(env, obj, fid_s); str = (*env)->GetStringUTFChars(env, jstr, NULL); if (str == NULL) { return; /* out of memory */ } printf(" c.s = \"%s\"\n", str); (*env)->ReleaseStringUTFChars(env, jstr, str); jstr = (*env)->NewStringUTF(env, "123"); if (jstr == NULL) { return; /* out of memory */ } (*env)->SetObjectField(env, obj, fid_s, jstr); }
同样的思想,我们也可以缓存 java.lang.String 的构造方法的ID:
jstring MyNewString(JNIEnv *env, jchar *chars, jint len) { jclass stringClass; jcharArray elemArr; static jmethodID cid = NULL; jstring result; stringClass = (*env)->FindClass(env, "java/lang/String"); if (stringClass == NULL) { return NULL; /* exception thrown */ } /* Note that cid is a static variable */ if (cid == NULL) { /* Get the method ID for the String constructor */ cid = (*env)->GetMethodID(env, stringClass, "<init>", "([C)V"); if (cid == NULL) { return NULL; /* exception thrown */ } } /* Create a char[] that holds the string characters */ elemArr = (*env)->NewCharArray(env, len); if (elemArr == NULL) { return NULL; /* exception thrown */ } (*env)->SetCharArrayRegion(env, elemArr, 0, len, chars); /* Construct a java.lang.String object */ result = (*env)->NewObject(env, stringClass, cid, elemArr); /* Free local references */ (*env)->DeleteLocalRef(env, elemArr); (*env)->DeleteLocalRef(env, stringClass); return result; }
当MyNewString方法第一次被调用时,我们计算 java.lang.String 的构造方法的ID,并存储在静态变量 cid中。
类的静态初始化过程中缓存字段和方法ID
例如,为了缓存InstanceMethodCall.callback 的方法ID,我们引入了一个新的本地方法initIDs,这个方法在InstanceMethodCall 的静态初始化过程中被调用。代码如下:
class InstanceMethodCall { private static native void initIDs(); private native void nativeMethod(); private void callback() { System.out.println("In Java"); } public static void main(String args[]) { InstanceMethodCall c = new InstanceMethodCall(); c.nativeMethod(); } static { System.loadLibrary("InstanceMethodCall"); initIDs(); } }
initIDs方法简单地计算并缓存方法ID:
jmethodID MID_InstanceMethodCall_callback; JNIEXPORT void JNICALL Java_InstanceMethodCall_initIDs(JNIEnv *env, jclass cls) { MID_InstanceMethodCall_callback = (*env)->GetMethodID(env, cls, "callback", "()V"); }
VM进行静态初始化时在调用任何方法前调用 initIDs,这样方法ID 就被缓存了全局变量中,本地方法的实现就不必再进行 ID计算:
JNIEXPORT void JNICALL Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj) { printf("In C\n"); (*env)->CallVoidMethod(env, obj,MID_InstanceMethodCall_callback); }
比起静态初始时缓存来说,使用时缓存有一些缺点:
1、使用时缓存的话,每次使用时都要检查一下。
2、方法ID和字段 ID在类被unload时就会失效,如果你在使用时缓存 ID,你必须确保只要本地代码依赖于这个 ID的值,那么这个类不被会 unload(下一章演示了如何通过使用 JNI函数创建一个类引用来防止类被 unload)。另一方面,如果缓存发生在静态初始化时,当类被 unload和reload 时,ID会被重新计算。
因此,尽可能在静态初始化时缓存字段 ID和方法 ID。