原文 : http://192.9.162.55/docs/books/jni/html/refs.html
JNI给出实例和数组类型(如jobject,jclass,jstring,jarray)作为不透明的引用。Native代码从不会直接访问一个不透明的引用的内容。相反,它使用JNI函数访问由一个不透明的参考指向数据结构。只处理不透明的引用,你不用担心依赖于特定的Java虚拟机实现的内部对象布局。但是你的确需要进一步了解JNI中不同类型的引用:
很多 JNI 函数创建局部引用。例如,JNI函数 New-Object 创建一个新的实例并返回这个实例的局部引用。
一个局部引用仅在创建它的native函数及该函数调用的函数中有效。在一个native函数执行期间创建的所有局部引用将在该函数返回时被释放。
你一定不能写一个native函数来保存一个局部引用在静态变量中并期望在以后的函数调用中使用它。例如,以下程序是4.4节中 MyNewString 函数的修改版本,他错误使用了局部引用。
/* This code is illegal */ jstring MyNewString(JNIEnv *env, jchar *chars, jint len) { static jclass stringClass = NULL; jmethodID cid; jcharArray elemArr; jstring result; if (stringClass == NULL) { stringClass = (*env)->FindClass(env, "java/lang/String"); if (stringClass == NULL) { return NULL; /* exception thrown */ } } /* 这儿使用缓冲过的 stringClass 是错误的,因为他可能是无效了 */ cid = (*env)->GetMethodID(env, stringClass, "<init>", "([C)V"); ... elemArr = (*env)->NewCharArray(env, len); ... result = (*env)->NewObject(env, stringClass, cid, elemArr); (*env)->DeleteLocalRef(env, elemArr); return result; }这儿省略了部分代码和我们要讨论的问题无关。用一个静态变量缓存stringClass的目地本来是要消除重复下面的函数调用的开销:
FindClass(env, "java/lang/String");这是不正确的做法,因为findClass返回java.lang.String类对象的局部引用。要明白为什么这是一个问题,假设比照调用Native方法的实现MyNewString的:
JNIEXPORT jstring JNICALL Java_C_f(JNIEnv *env, jobject this) { char *c_str = ...; ... return MyNewString(c_str); }Native方法C.f 返回后,虚拟机释放了执行Java_C_f 期间创建的所有局部引用。这些被释放的局部引用包含存储在stringClass变量中的对类对象局部引用。以后的MyNewString 将试图使用无效的局部引用,这可能会导致内存数据损坏或系统崩溃。例如,下面的Java代码段连续两次调用C.f 并造成 MyNewString 遭遇到无效的局部引用:
... ... = C.f(); // The first call is perhaps OK. ... = C.f(); // This would use an invalid local reference. ...有两种方法使一个局部引用无效。如前所述,当native方法返回后,虚拟机自动释放了方法执行期间创建的所有局部引用。另外,程序员可以用像DeleteLocalRef 这样的JNI函数显式管理局部引用的生命周期。
一个局部引用在被销毁前可能在多个native方法件被传递。例如,MyNewString 返回NewObject创建的字符串引用,然后它将被传给 MyNewString 的调用者以决定是否释放MyNewString 返回的局部引用。 在Java_C_f 例子中,C.f 返回 MyNewString 返回的结果。虚拟机从 Java_C_f 函数收到局部引用之后,它传递下层的字符串对象给 C.f 的调用者,并且随后销毁原本由JNI函数NewObject 创建的局部引用。
局部引用也仅在创建它们的线程中有效。在一个线程中创建的局部引用不能为另一个线程所用。在native 方法中存储一个局部引用到全局变量并期望在另一个线程中使用它是错误的
/* This code is OK */ jstring MyNewString(JNIEnv *env, jchar *chars, jint len) { static jclass stringClass = NULL; ... if (stringClass == NULL) { jclass localRefCls = (*env)->FindClass(env, "java/lang/String"); if (localRefCls == NULL) { return NULL; /* exception thrown */ } /* Create a global reference */ stringClass = (*env)->NewGlobalRef(env, localRefCls); /* The local reference is no longer useful */ (*env)->DeleteLocalRef(env, localRefCls); /* Is the global reference created successfully? */ if (stringClass == NULL) { return NULL; /* out of memory exception thrown */ } } ... }修改后的版本将 FindClass返回的局部引用传递给 NewGlobalRef, NewGlobalRef 创建一个 java.lang.String 类对象的全局引用。我们在删除localRefCls 后检查 NewGlobalRef 是否成功创建stringClass,因为无论检查结果如何,局部引用 localRefCls 都需要被删除。
JNIEXPORT void JNICALL Java_mypkg_MyCls_f(JNIEnv *env, jobject self) { static jclass myCls2 = NULL; if (myCls2 == NULL) { jclass myCls2Local = (*env)->FindClass(env, "mypkg/MyCls2"); if (myCls2Local == NULL) { return; /* can't find class */ } myCls2 = NewWeakGlobalRef(env, myCls2Local); if (myCls2 == NULL) { return; /* out of memory */ } } ... /* use myCls2 */ }我们假设 MyCls 和 MyCls2 有相同的生命周期。(例如,它们可能被相同的类加载器(loader)所加载)那么我们不考虑 MyCls2 何时被卸载及之后当MyCls和它的native方法实现 Java_mypkg_MyCls 保持使用时的重新载入。 如果这些发生,我们理应检查缓存的弱全局引用是指向一个有效的类对象还是已经被garbage收集的类对象。下一节将解释如何对弱全局引用进行那样的检查。
(*env)->IsSameObject(env, obj1, obj2)如果obj1 和 obj2指向相同对象,函数返回 JNI_TRUE (或 1),否则返回 JNI_FALSE (或 0)。
(*env)->IsSameObject(env, obj, NULL)或
obj == NULL来决定obj 是否指向 null 对象。
(*env)->IsSameObject(env, wobj, NULL)如果 wobj 指向一个已经被收集的对象,还是会返回 JNI_TRUE 并且如果 wobj 还指向有效对象,返回 JNI_FALSE。
for (i = 0; i < len; i++) { jstring jstr = (*env)->GetObjectArrayElement(env, arr, i); ... /* process jstr */ (*env)->DeleteLocalRef(env, jstr); }
/* A native method implementation */ JNIEXPORT void JNICALL Java_pkg_Cls_func(JNIEnv *env, jobject this) { lref = ... /* a large Java object */ ... /* last use of lref */ (*env)->DeleteLocalRef(env, lref); lengthyComputation(); /* may take some time */ return; /* all local refs are freed */ }
/* The number of local references to be created is equal to the length of the array. */ if ((*env)->EnsureLocalCapacity(env, len)) < 0) { ... /* out of memory */ } for (i = 0; i < len; i++) { jstring jstr = (*env)->GetObjectArrayElement(env, arr, i); ... /* process jstr */ /* DeleteLocalRef is no longer necessary */ }当然,以上版本可能会消耗更多内存。
#define N_REFS ... /* 每次遍历中使用到的局部引用的最大数目 */ for (i = 0; i < len; i++) { if ((*env)->PushLocalFrame(env, N_REFS) < 0) { ... /* out of memory */ } jstr = (*env)->GetObjectArrayElement(env, arr, i); ... /* process jstr */ (*env)->PopLocalFrame(env, NULL); }PushLocalFrame 为特定数量的局部引用创建一个新的作用域。 PopLocalFrame 销毁最顶层作用域,释放这个作用域中的所有局部引用。
当 native代码不再需要访问一个弱全局引用,你应当调用 DeleteWeakGlobalRef。 如果没有成功调用这个函数,Java虚拟机garbage也会收集这个对象。但弱全局引用本身消耗的内存将无法回收。
while (JNI_TRUE) { jstring infoString = GetInfoString(info); ... /* process infoString */ ??? /* we need to call DeleteLocalRef, DeleteGlobalRef, or DeleteWeakGlobalRef depending on the type of reference returned by GetInfoString. */ }在 Java 2 SDK r 1.2中,NewLocalRef 函数某些时候对于确保一个工具函数总是返回一个局部引用是有用的。为了说明,让我们对MyNewString 刻意做些改动。下面版本将一个被频繁请求的字符串还存在一个全局引用中(称作 "CommonString") :
jstring MyNewString(JNIEnv *env, jchar *chars, jint len) { static jstring result; /* wstrncmp compares two Unicode strings */ if (wstrncmp("CommonString", chars, len) == 0) { /* refers to the global ref caching "CommonString" */ static jstring cachedString = NULL; if (cachedString == NULL) { /* create cachedString for the first time */ jstring cachedStringLocal = ... ; /* cache the result in a global reference */ cachedString = (*env)->NewGlobalRef(env, cachedStringLocal); } return (*env)->NewLocalRef(env, cachedString); // <<<<<< 注意这个返回的引用类型是局部引用 } ... /* create the string as a local reference and store in result as a local reference */ return result; }一般返回一个字符串是作为局部引用。如以前解释的那样,我们必须将缓存的字符串保存在一个全局引用中,以便能被多个native方法调用访问或多个线程调用。提请注意的行创建了一个指向全局引用 cachedString 的局部引用。作为和调用者的约定, MyNewString 总是返回一个局部引用。
jobject f(JNIEnv *env, ...) { jobject result; if ((*env)->PushLocalFrame(env, 10) < 0) { /* frame not pushed, no PopLocalFrame needed */ return NULL; } ... result = ...; if (...) { /* remember to pop local frame before return */ result = (*env)->PopLocalFrame(env, result); return result; } ... result = (*env)->PopLocalFrame(env, result); /* normal return */ return result; }没有正确放置 PopLocalFrame 调用将导致未定义行为,例如,虚拟机崩溃。
小贴士: JNIEnv *pEnv 是不能被直接保存起来供回调函数或线程使用的, 而 JavaVM 是可以的,所以需要用 *pEnv)->GetJavaVM 获得 JavaVM 并保存。然后在适当地方 用JavaVM -> AttachCurrentThread 函数取回 JNIEnv。
翻译中使用特定词汇个人理解和译法很可能有差异,可能您对这样的译法有不同的想法,仅希望这张词汇表对您的理解有所帮助:
F
function : 函数 / 功能
M
method : 方法
O
overflow : 溢出
R
reference :引用
local references : 局部引用
global references : 全局引用
weak global references:弱全局引用