- 局部引用和全局引用
- 释放引用
- 管理引用的规则
JNI提供了一些实例和数组类型(jobject,jclass,jstring,jarray等)作为不透明的引用供本地代码使用。本地代码永远不会直接操作引用指向VM内部的数据内容。要进行这些操作,必须通过使用JNI操作引用来间接操作这些数据内容。JNI中几种不同的引用:
- JNI支持三种引用:本地引用,全局引用和弱全局引用。
- 局部引用和全局引用有不同的生命周期。当本地方法返回时,局部引用会被自动释放。而全局引用和弱引用必须手动释放。
- 局部引用或者全局引用会阻止GC回收它们所引用的对象,而弱引用不会。
一、局部引用和全局引用
1.1 局部引用
大多数JNI函数会创建局部引用,
1.1.1 NewLocalRef
创建指向ref的本地引用。
jobject NewLocalRef(JNIEnv *env, jobject ref);
1.1.2 DeleteLocalRef
删除localRef所指向的本地引用。一般情况下本地引用由虚拟机自动释放,但是程序员可以选择手动释放占用内存较大的临时空间,譬如数组,大字符串等。
void DeleteLocalRef(JNIEnv *env, jobject localRef);
1.1.3 示例
以下代码段演示了用全局变量记录本地引用的问题,stringClass为static变量,保存了String class的本地引用。本地引用会在函数调用之后被释放,当尝试再次调用函数从静态变量中获取时由于本地引用已经释放,会造成非法访问异常。
jstring MyNewString(JNIEnv *env, jchar *chars, jint len) {
static jclass stringClass = nullptr;
jmethodID cid;
jcharArray elemArr;
jstring ret;
if (nullptr == stringClass) {
stringClass = env->FindClass("java/lang/String");
if (nullptr == stringClass) {
return nullptr;
}
}
cid = env->GetMethodID(stringClass, "<init>", "([C)V");
elemArr = env->NewCharArray(len);
//It is wrong to use the cached stringClass here,because it may be invalid.
ret = reinterpret_cast<jstring>(env->NewObject(stringClass, cid, elemArr));
//Notify garbage to release elemArr immediately.
env->DeleteLocalRef(elemArr);
return ret;
}
使用stringClass进行缓存的目的是减少FindClass的操作,这里的风险在于缓存的对象是一个局部引用。
JNIEXPORT jstring JNICALL Java_C_f(JNIEnv *env,jobject this)
{
char *c_str = ...;
...
return MyNewString(c_str);
}
当C.f调用返回时,虚拟机会释放所有方法调用期间产生的局部引用。
... = C.f(); // The first call is perhaps ok.
... = C.f(); // This would use an invalid local reference.
1.2 全局引用
全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效。同局部引用一样,全局引用会阻止它所引用的对象被GC回收。
1.2.1 NewGlobalRef
根据obj创建新的全局引用。全局引用必须通过DeleteGlobalRef进行手动释放。
jobject NewGlobalRef(JNIEnv *env, jobject obj);
1.2.2 DeleteGlobalRef
删除全局引用。
void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
1.2.3 示例
与局部引用可以被大多数JNI函数创建不同,全局引用只能使用NewGlobalRef这一个JNI函数创建:
jstring MyNewString(JNIEnv *env, jchar *chars, jint len) {
static jclass stringClass = nullptr;
jmethodID cid;
jcharArray elemArr;
jstring ret;
if (nullptr == stringClass) {
jclass lStringClass = env->FindClass("java/lang/String");
if (nullptr == lStringClass) {
return nullptr;
}
stringClass = reinterpret_cast<jclass>(env->NewGlobalRef(lStringClass));
env->DeleteLocalRef(lStringClass);
if(nullptr == stringClass) {
return nullptr;
}
}
cid = env->GetMethodID(stringClass, "<init>", "([C)V");
elemArr = env->NewCharArray(len);
ret = reinterpret_cast<jstring>(env->NewObject(stringClass, cid, elemArr));
env->DeleteLocalRef(elemArr);
return ret;
}
上述代码中,一个由FindClass返回的局部引用被传入NewGlobalRef,用来创建一个对String类的全局引用。删除lStringClass后,校验NewGlobalRef是否成功创建stringClass。
1.3 弱全局引用
弱全局引用使用NewWeakGlobalRef创建,使用DeleteWeakGlobalRef释放。与全局引用类似,弱全局引用可以跨方法、线程使用。与全局引用不同的是,弱全局引用不会阻止GC回收它所引用的VM内部对象。
弱全局引用在引用强度上比弱引用(SoftReference&WeakReference)还要弱。
1.3.1 NewWeakGlobalRef
创建一个新的弱引用。
jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);
1.3.2 DeleteWeakGlobalRef
删除弱引用。
void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);
1.4 引用比较
给定两个引用(不管是全局、局部还是弱引用),可以使用IsSameObject来判断它们两个是否指向相同的对象。
jboolean IsSameObject(jobject ref1, jobject ref2)
{ return functions->IsSameObject(this, ref1, ref2); }
如果ref1和ref2指向相同的对象,返回JNI_TRUE,否则返回JNI_FALSE。
二、释放引用
每一个JNI引用被建立时,除了它所指向的JVM中的对象以外,引用本身也会消耗掉一个数量的内存。短时间内创建大量不会被立即回收的引用会导致内存溢出。
2.1 释放局部引用
大多数情况下,在实现本地方法时不必担心局部引用的释放问题,因为本地方法被调用完成后,JVM会自动回收这些引用。尽管如此,以下几种情况下,为了避免内存溢出,JNI程序员应该手动释放局部引用:
1. 在本地方法中需要创建大量的局部引用。这种情况可能会导致JNI局部引用表的溢出,所以,最好是在局部引用不需要时手动删除。
for (int i = 0; i < len; i++) {
jstring jstr = env->GetObjectArrayElement(arr,i);
/*Process jstr*/
env->DeleteLocalRef(jstr);
}
- 本地方法访问大对象,因此创建了对大对象的引用。如果本地方法返回前会有一个做大量计算的过程,而这个过程中是不需要前面创建的对大对象的引用的。但是,计算过程,对大对象的引用会阻止GC回收大对象。
2.2 管理局部引用
JDK提供了一系列的函数来管理局部引用的生命周期。这些函数包括:EnsureLocalCapacity、NewLocalRef、PushLocalFrame、PopLocalFrame。
2.2.1 EnsureLocalCapacity
保证能够创建至少指定capacity大小的本地引用。返回0代表成功,否则失败。
在进行本地方法之前,VM会自动确保至少16个本地引用可以被创建。
jint EnsureLocalCapacity(JNIEnv *env, jint capacity);
2.2.2 PushLocalFrame
创建新的局部引用帧,在帧中至少可以创建指定capacity大小的局部引用。返回0代表成功,否则失败。
jint PushLocalFrame(JNIEnv *env, jint capacity);
2.2.3 PopLocalFrame
弹出当前的局部引用帧,释放所有的局部引用。返回之前局部引用帧中对result对象的局部引用。
jobject PopLocalFrame(JNIEnv *env, jobject result);
2.3 释放全局引用
当本地代码不再需要一个全局引用时,应该调用DeleteGlobalRef来释放它。否则,JVM不会回收这个全局引用所指向的对象。
当本地代码不再需要一个弱引用时,应该调用DeleteWeakGlobalRef来释放它,否则,虽然JVM会回收弱引用所指向的对象,但弱引用本身在引用表所占的内存永远不会被回收。
三、管理引用的规则
JNI引用的管理规则为:减少内存使用和对象被引用而不能释放:
- 在编写本地方法代码时,当心不要造成全局引用和弱引用的累加,因为本地方法执行完毕后,这两种应用不会被自动释放。
- 当编写工具函数的本地代码时,当心不要在函数的调用轨迹上面遗漏任何的局部引用,因为工具函数被调用的场合是不确定的,一旦被大量调用,很有可能造成内存溢出。
编写工具函数时,请遵守下面的规则:
- 一个返回值为基本类型的工具函数被调用时,决不能造成局部、全局、弱引用不被回收的累加。
- 一个返回值为引用类型的工具函数被调用时,除了返回的引用以外,决不能造成其他局部、全局、弱引用的累加。
网友评论