1. 概述:
在JNI中 支持三种难懂的引用,分别是:本地引用(local references),全局引用( global references),弱全局引用(weak global references.)。
- 本地引用和全局引用有不同的生命周期,本地引用能被自动释放,而全局引用和弱全局引用需要程序员来释放。
- 本地引用和全局引用阻止所引用的对象被垃圾回收,另一方面,弱全局引用允许所引用的对象被垃圾回收
- 不是所有的引用能在所有的上下文(context)中使用,例如 :使用本地方法中创建的,之后返回的本地引用是非法的。
2.本地引用和全局引用(Local and Global References)
本地引用和全局引用是什么?有什么不同?将使用一系列的例子来描述他们。
2.1.本地引用(Local References)
多数JNI 函数创建本地引用,例如, JNI 函数NewObject 创建一个新的实例,以及返回这个实例的本地引用,本地引用仅仅在创建它的本地方法的动态上下文 以及本地方法的一个调用中有效。所有在一个本地方法执行期间创建的本地引用,一旦本地方法返回,将被释放。禁止在本地方法中,存储一个本地引用到一个静态变量,希望在后续的调用中使用相同的引用。请看如下例子:
/* This code is illegal */
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
static jclass stringClass = NULL;
jcharArray elemArr;
//static jmethodID cid = NULL;
jmethodID cid = NULL;
jstring result;
ALOGI(" MyNewString::chars = %s, len=%d\n", chars,len);
if (stringClass == NULL) {
stringClass = env->FindClass("java/lang/String");
if (stringClass == NULL) {
return NULL; /* exception thrown */
}
}
/* It is wrong to use the cached stringClass here,
because it may be invalid. */
/* Note that cid is a static variable */
//if (cid == NULL) {
/* Get the method ID for the String constructor */
cid =env->GetMethodID(stringClass,"<init>", "([C)V");
if (cid == NULL) {
return NULL; /* exception thrown */
}
//}
/* Create a char[] that holds the string characters */
elemArr = env->NewCharArray(len);
if (elemArr == NULL) {
return NULL; /* exception thrown */
}
env->SetCharArrayRegion(elemArr, 0, len, chars);
for(int i=0;i<len;i++){
ALOGI(" MyNewString::elemArr[%d] = %c\n", i,*(elemArr+i));
}
/* Construct a java.lang.String object */
result = (jstring)env->NewObject(stringClass, cid, elemArr);
//ALOGI(" MyNewString::result = %s\n", result);
/* Free local references */
env->DeleteLocalRef(elemArr);
//env->DeleteLocalRef(stringClass);
return result;
}
这段代码的使用静态变量stringClass的目的是避免重复调用“FindClass(env, "java/lang/String"); ”,但是FindClass是返回的是java.lang.String类对象的本地引用,为说明为什么不对,可以看看本地方法simplejni_referencesExample中对该函数调用的实现:
void simplejni_referencesExample(JNIEnv *env, jobject obj){
char *c_str = ...;
...
return MyNewString(c_str ...);
}
在simplejni_referencesExample 返回后VM 释放在 simplejni_referencesExample 所创建的所有的本地引用,包括stringClass 变量,在未来接下来对MyNewString的调用,将使用非法的本地引用,将导致内存损坏(memory corruption)或系统崩溃(system crashes)。例如,在java 中两次调用本地方法如下代码:
...
... = C.referencesExample(); // The first call is perhaps OK.
... = C.referencesExample(); // This would use an invalid local reference. the system will crash.
...
除了本地引用在本地方法返回的时候自动释放外,也可以使用JNI 函数 DeleteLocalRef 主动释放,在MyNewString中使用“env->DeleteLocalRef(elemArr);”使“elemArr”数组立即被回收。如果不调用DeleteLocalRef(elemArr)去释放elemArr, 那只有在本地方法调用MyNewString返回后才能够被释放。
本地方法可以在多个本地方法中传递。例如:MyNewString返回NewObject创建的string 引用,然后传递到simplejni_referencesExample本地方法,再次传递给调用C.referencesExample()的java 层。然后将毁坏由NewObject创建的本地引用。
本地引用也仅仅在创建它们的线程中有效,在一个线程中创建的本地引用,不能在另一个线程中使用。在本地方法使用一个全局变量存储本地引用期望给其他线程使用该本地引用犯了编程的错误。
2.2 全局引用(Global References)
全局引用用可以在多个本地方法调用,多个线程中交叉使用,直到主动调用DeleteGlobalRef函数释放它,不然一直有效。不像本地引用一样,可以通过多个JNI函数去创建它,例如:NewObject,NewLocalRef等等,全局引用仅仅只能使用JNI 函数NewGlobalRef来创建。继续使用MyNewString例子来描述如何使用全局引用。
/* This code is OK */
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
static jclass stringClass = NULL;
jcharArray elemArr;
//static jmethodID cid = NULL;
jmethodID cid = NULL;
jstring result;
ALOGI(" MyNewString::chars = %s, len=%d\n", chars,len);
if (stringClass == NULL) {
jclass localRefCls =env->FindClass( "java/lang/String");
if (localRefCls == NULL) {
return NULL; /* exception thrown */
}
/* Create a global reference */
stringClass =(jclass) env->NewGlobalRef(localRefCls);
/* The local reference is no longer useful */
env->DeleteLocalRef(localRefCls);
/* Is the global reference created successfully? */
if (stringClass == NULL) {
return NULL; /* out of memory exception thrown */
}
}
/* It is wrong to use the cached stringClass here,
because it may be invalid. */
/* Note that cid is a static variable */
//if (cid == NULL) {
/* Get the method ID for the String constructor */
cid =env->GetMethodID(stringClass,"<init>", "([C)V");
if (cid == NULL) {
return NULL; /* exception thrown */
}
//}
/* Create a char[] that holds the string characters */
elemArr = env->NewCharArray(len);
if (elemArr == NULL) {
return NULL; /* exception thrown */
}
env->SetCharArrayRegion(elemArr, 0, len, chars);
for(int i=0;i<len;i++){
ALOGI(" MyNewString::elemArr[%d] = %c\n", i,*(elemArr+i));
}
/* Construct a java.lang.String object */
result = (jstring)env->NewObject(stringClass, cid, elemArr);
//ALOGI(" MyNewString::result = %s\n", result);
/* Free local references */
env->DeleteLocalRef(elemArr);
//env->DeleteLocalRef(stringClass);
return result;
}
使用以上代码,再次测试如下代码,就不会再有system crash 了。
...
... = C.referencesExample(); // The first call is OK.
... = C.referencesExample(); // The second call is also OK.
...
2.3 弱全局引用(Weak Global References)
弱全局引用是Java 2 SDK release 1.2 版本新引入的,使用JNI 函数NewWeakGlobalRef 创建该引用,使用JNI 函数DeleteWeakGlobalRef删除该引用,像全局引用一样,在本地方法间调用以及不同的线程间交叉使用,但不能阻止它所引用的对象被垃圾回收。这里继续使用MyNewString例子来描述弱全局引用的使用,只要简单使用“ NewWeakGlobalRef”替代一下“ NewGlobalRef”即可。使用弱全局引用存储被缓冲的(cache) java.lang.String 类,我们不必担心使用弱全局引用之后,会出现什么问题,因为java.lang.String是系统类,永远不会被垃圾回收。
一个被本地代码缓冲的引用允许它的所引用的对象能被垃圾回收,这时弱全局应用显得更有用。例如: 假设一个本地方法mypkg.MyCls.f 需要对类class mypkg.MyCls2缓冲(cache)一个引用,用一个弱全局引用缓冲该类,依然允许mypkg.MyCls2类被 卸载。
void mypkg_MyCls_f(JNIEnv *env, jobject self)
{
static jclass myCls2 = NULL;
if (myCls2 == NULL) {
jclass myCls2Local =env->FindClass( "mypkg/MyCls2");
if (myCls2Local == NULL) {
return; /* can\u2019t find class */
}
myCls2 = env->NewWeakGlobalRef(myCls2Local);
if (myCls2 == NULL) {
return; /* out of memory */
}
}
//... /* use myCls2 */
}
这里假设MyCls 和 MyCls2 类有同样的生命周期(例如可以被同一个类loaded),这样需要考虑这种情况,当MyCls2被unloaded 和之后reloaded,而MyCls 和它的本地方法实现mypkg_MyCls 依然保持使用.我们必须确认是否缓冲的弱全局引用仍然指向一个活动的类对象或者指向一个已被垃圾回收的类对象。
2.4 。比较引用(Comparing References)
给定两个本地引用,全局应用或者弱全局引用,使用JNI 函数IsSameObject确定是否指向同一个对象。
env->IsSameObject(obj1, obj2);
JNI 中一个NULL 引用,引用JAVA VM 中的null 对象,如果 obj 表示一个本地引用或全局引用,可以使用如下表达式确定obj是否引用了null 对象:
env->IsSameObject(obj1, NULL);
或者
obj == NUL
以上规则对于弱全局引用有些不同,IsSameObject对弱全局引用有特别的用途,可以使用 IsSameObject确定是否一个 non-NULL 弱全局引用依然指向一个活动对象,假设wobj是一non-NULL 弱全局引用,如下调用:
env->IsSameObject(wobj, NULL);
如果wobj 引用一个已被回收的对象, 返回JNI_TRUE,否则,仍然引用一个活动对象,返回JNI_FALSE。
3. 释放引用(Freeing References)
除了被引用对象占用内存外,每个JNI 引用自身也消耗掉一定数量的内存(memory),需要考虑在一给定的时间程序使用引用数,特别需要考虑程序运行期间,任何时间点能够创建本地引用数的上限,即便本地引用最终会自动被VM 释放掉。瞬间过度地引用创建,可能会导致内存消耗殆尽。
3.1 释放本地引用(Freeing Local References)
大多情况下,不需要担心在实现本地方法的时候本地引用的释放问题。当本地方法返回的时候,会自动释放,但是如下情况需要调用JNI 函数DeleteLocalRef来释放。
- 在本地方法中需要创建大量的本地引用,可能导致内部JNI 本地引用表溢出,好的方法是迅速删除不再被使用的本地引用。例如:
for (i = 0; i < len; i++) {
jstring jstr = env->GetObjectArrayElement(arr, i);
... /* process jstr */
env->DeleteLocalRef(jstr);
}
- 你想写一个工具性的函数供未知名的上下文调用,需要迅速删除未用的本地引用.
- 本地方法根本不会返回,例如:一个本地方法可能是一个事件分配的循环,在循环内需要释放在该循环内创建的本地引用,避免一直本地引用数的累加,导致内存泄漏。
- 本地方法中访问大的对象,从而创建一个本地引用引用该对象,本地方法然后在返回前,执行附件的费时计算,本地引用将阻止该对象的释放,直到本地方法返回,即使该对象在剩下的代码中不再被使用。 例如:
/* A native method implementation */
void pkg_Cls_func(JNIEnv *env, jobject this)
{
lref = ...
/* a large Java object */
...
/* last use of lref */
env->DeleteLocalRef(lref);
lengthyComputation();
return;
/* may take some time */
/* all local refs are freed */
}
3.2 管理本地引用(Managing Local References)
从Java 2 SDK release 1.2版本开始, 提供一套额外的管理本地引用生命周期的函数,他们是:
jobject NewLocalRef(jobject ref)
jint EnsureLocalCapacity(jint capacity)
jint PushLocalFrame(jint capacity)
jobject PopLocalFrame(jobject result)
JNI 规范中,一个VM 自动确保每个本地方法能够创建至多16个本地引用,经验表明在JAVA虚拟机中,这对本地方法不包括复杂的相互作用的对象是足够了,然而,需要创建额外的本地引用,本地方法可以调用JNI 函数EnsureLocalCapacity,确保足够本地引用有效。
经验表明这提供了足够的能力对大多数本机方法不包含复杂的相互作用,在 Java 虚拟机中的对象。
/* The number of local references to be created is equal to
the length of the array. */
if (env->EnsureLocalCapacity(len)) < 0) {
... /* out of memory */
}
for (i = 0; i < len; i++) {
jstring jstr = env->GetObjectArrayElement(arr, i);
... /* process jstr */
/* DeleteLocalRef is no longer necessary */
}
另外,JNI 函数push/PopLocalFrame 允许创建嵌套的本地引用的范围。例如:
#define N_REFS ... /* the maximum number of local references
used in each iteration */
for (i = 0; i < len; i++) {
if (env->PushLocalFrame(N_REFS) < 0) {
... /* out of memory */
}
jstr = env->GetObjectArrayElement(arr, i);
... /* process jstr */
env->PopLocalFrame(NULL);
}
PushLocalFrame 创建新的范围, 而PopLocalFrame毁坏的范围,释放在该范围的所有本地引用。
3.3 释放全局引用(Freeing Global References)
当本地代码中不再需要访问全局引用的时候,您应该调用DeleteGlobalRef去释放。如果调用失败JAVA 虚拟机将不会回收相应的对象。 甚至在系统中其他任何地方该对象不再被使用。
可以调用JNI 函数DeleteWeakGlobalRef释放不再使用的弱全局引用对象,如果调用失败,虚拟机依然能够回收该引用的对象,但是不能够从新申请被弱引用消耗掉的内存。