简介
- 在获取一个 属性/方法ID 的时候需要基于名称或者 属性/方法描述符 的 符号查找 , 符号查找 的代价相对来说是比较昂贵的(消耗时间和资源),优化主要思路的是计算 属性/方法ID 通过缓存以供后续使用。
- 有两种缓存方式,如有可能应该尽量使用后者: 用时缓存 、 类静态初始化器缓存 。
用时缓存
简单示例
JNIEXPORT void JNICALL Java_InstanceFieldAccess_accessField(JNIEnv * env, jobject obj) {
// 静态局部变量以便于一次获取,以后就不必重新获取
static jfieldID fid_s = NULL;
jclass cls = (* env)->GetObjectClass(env, obj);
// 只有从来没获取fid_s才会获取
if (!fid_s) {
fid_s = (* env)->GetFieldID(env, cls, "s", "Ljava/lang/String;");
if (!fid_s) {
return;
}
}
jstring jstr = (* env)->GetObjectField(env, obj, fid_s);
const char * str = (* env)->GetStringUTFChars(env, jstr, NULL);
if (!str) {
return;
}
printf("In C: \n");
printf("c.s = \"%s\"\n", str);
(* env)->ReleaseStringUTFChars(env, jstr, str);
jstring newJstr = (* env)->NewStringUTF(env, "123");
if (!newJstr) {
return;
}
(* env)->SetObjectField(env, obj, fid_s, newJstr);
}
示例解说
- 示例中的属性ID
fid_s
使用了静态局部存储,用以缓存,重复使用时不再重新获取。
优缺点
-
优点 :非侵入性的,即在对 Java 代码没有控制权的时候,仍然可以用 用时缓存 ,而 类静态初始化器缓存 的方法则不可以。
-
缺点 :
- 在多线程的情况下,可能会出现
fid_s
被 重复计算/缓存/检查 的问题。在此处fid_s
被 重复计算/缓存 除了有一部分性能开销以外,基本是无害的。
- 当类被载出时,缓存的ID不再有效,需要获取,在使用 用时缓存 的方法时,是需要保证当原生代码仍然在用缓存ID时,类不会被载出或重新加载。
类静态初始化器缓存
简单示例
public class InstanceMethodCall {
static {
System.loadLibrary("InstanceMethodCall");
// 在类静态初始化代码块中缓存ID
initIDs();
}
public static void main(String args[]) {
InstanceMethodCall c = new InstanceMethodCall();
c.nativeMethod();
}
// 类静态函数,实现交给原生代码,用于缓存ID
private static native void initIDs();
private native void nativeMethod();
private void callback() {
System.out.println("In Java");
}
}
// 创建全局变量,以便于ID可以在多个不同的原生函数中传递
jmethodID MID_InstanceMethodCall_callback;
JNIEXPORT void JNICALL Java_InstanceMethodCall_initIDs(JNIEnv * env, jclass cls) {
// 获取所有需要缓存的ID到全局变量中
MID_InstanceMethodCall_callback = (* env)->GetMethodID(env, cls, "callback", "()V");
}
JNIEXPORT void JNICALL Java_InstanceMethodCall_nativeMethod(JNIEnv * env, jobject obj) {
printf("In C\n");
// 使用缓存的ID
(* env)->CallVoidMethod(env, obj, MID_InstanceMethodCall_callback);
}
示例解说
- 在 Java 代码中声明一个静态
native
函数initIDs()
用于缓存ID。
- 在 Java 代码中 类静态构造代码块 中调用
initIDs()
。
- 在原生代码中实现
initIDs()
,缓存ID到全局变量。
优缺点
-
优点 :不需要手动获取缓存ID,当类载入或重新加载时,ID会自动缓存。
-
缺点 :侵入性,无法在对 Java 代码没有控制权的情况下使用。
两种方法的性能比较
概念
-
Java/native调用 :原生代码调用 Java 函数。
-
native/Java调用 : Java 代码调用原生函数。
-
Java/Java调用 : Java 代码调用 Java 函数。
Java/native调用
-
Java/native调用 可能慢于 Java/Java调用 ,原因是:
- 不得不执行额外的操作。
- 原生代码调用 Java 函数难以内联。
- 经典的虚拟机执行 Java/native调用 会2-3倍慢于 Java/Java调用 ,但是构建一个虚拟机使得二者的性能开销接近甚至相等也是可能的。
native/Java调用
- 理论上 native/Java调用 相比 Java/Java调用 会慢2-3倍,但是 native/Java调用 比较罕见。