释放引用
每一个JNI引用被建立时,除了它所指向的JVM中的对象外,引用本身也会消耗掉一定数量的内存。
释放局部引用
大部分情况下,实现本地方法时不必担心局部引用的释放问题,因为本地方法被调用完成后,JVM会自动回收这些局部引用。
尽管如此,以下几种情况,为了避免内存溢出,应该手动释放局部引用:
1.在实现一个本地方法调用时,你需要创建大量的局部引用。这种情况啃根会导致JNI局部引用表的溢出,所以最好在局部引用不需要时立即手动删除。
比如:
for(i = 0; i < len; i++){
jstring jstr = (*env).GetObjectArrayElement(arr, i);
....
(*env).DeleteLocalRef(jstr);
}
2.你想写一个工具函数,这个函数被谁调用是不知道的。
比如在工具函数类使用引用后,使用DeleteLocalRef删除。
3.你的本地方法不会返回任何东西。例如:一个本地方法可能会在一个事件接收循环里面被调用,这种情况下,为了不让局部引用累积造成内存溢出,手动释放也是必须的。
4.你的本地方法访问一个大对象,因此创建了一个对这个大对象的引用。然后本地方法在返回前会有一个做大量的计算过程,而在这个过程中时不需要前面创建的对大对象的引用的。但是,在计算过程,对大对象的引用会阻止GC回收大对象。
管理局部引用
JDK 提供了一系列的函数来管理局部引用的生命周期。这些函数包括: EnsureLocalCapacity、NewLocalRef、PushLocalFrame、PopLocalFrame。
JNI 规范中指出,VM 会确保每个 地方法可以创建至少 16 个局部引用。经验表 明,这个数量已经满足大多数不需要和 JVM 中的内部对象有太多交互的 地方法。 如果真的需要创建更多的引用, 地方法可以通过调用 EnsureLocalCapacity 来支持更多的局部引用。
if((*env).EnsureLocalCapacity(len) < 0){
//out of memory
}
PushLocalFrame 为一定数量的局部引用创建了一个使用堆栈,而 PopLocalFrame 负责销毁堆栈顶端的引用。
Push/PopLocalFrame 函数对提供了对局部引用的生命周期更方便的管理。上面 的例子中,如果处理 jstr 的过程中创建了局部引用,则 PopLocalFrame 执行时, 这些局部引用全部会被销毁。
for(i = 0; i < len; i++){
if((*env).PushLocalFrame(N_REFS) < 0){
//out of memory
}
jstr = (*env).GetObjectArrayElement(arr, i);
(*env).PopLocalFrame(NULL);
}
当你写一个会返回局部引用的工具函数时,NewLocalRef 非常有用。
地代码可能会创建大量的局部引用,其数量可能会超过 16 个或 PushLocaFrame
和 EnsureLocalCapacity 调用设置的个数。VM 可能会尝试分配足够的内 存,但不能够保证分配成功。如果失败,VM 会退出。
释放全局引用
当你的 地代码不再需要一个全局引用时,你应该调用 DeleteGlobalRef 来释放 它。如果你没有调用这个函数,即使这个对象已经没用了,JVM 也不会 回收这个全局引用所指向的对象。
当你的 地代码不再需要一个弱引用时,应该调用 DeleteWeakGlobalRef 来释放 它,如果你没有调用这个函数,JVM 仍会回收弱引用所指向的对象,但 弱引用 身在引用表中所占的内存永远也不会被回收。
管理引用的规则
JNI 引用的管理规则了, 目标就是减少内存使用和对象被引用保持而不能释放。
通常情况下,有两种 地代码:直接实现 地方法的 地代码和可以被使用在任 何环境下的工具函数。
1.当编写实现 地方法的 地代码时,当心不要造成全局引用和弱引用的累加,因 为 地方法执行完毕后,这两种引用不会被自动释放。
2.当编写一个工具函数的 地代码时,当心不要在函数的调用轨迹上面遗漏任何的 局部引用,因为工具函数被调用的场合是不确定的,一旦被大量调用, 很有可能造成内存溢出。
编写工具函数时,应遵守的规则
1.一个返回值为基本类型的工具函数被调用时,它绝不能造成局部、全局、弱引用不被回收的累加。
2.当一个返回值为引用类型的工具函数被调用时,它除了返回的引用以外,绝不能造成其它局部、全局、弱引用的累加。
如果一个工具函数返回一个引用,你应该详细说明返回的引用类型,以便调用者更好的管理它们。
例如:
while(JNI_TRUE){
jstring infoString = GetInfoString(info);
//我们需要知道infoString的引用类型,然后调用DeleteLcoalRef、DeleteGlobalRef、DeleteWeakGlobalRef。
}
函数NewLocalRef有时被用来确保一个工具函数返回一个局部引用
jstring MyNewString(JNIEnv *env){
static jstring cachedString = NULL;
if(cachedString == NULL){
jclass stringClass = (*env).FindClass("java/lang/String");
//创建cachedStringLocal
jstring cachedStringLocal = (jstring) (*env).NewObject(stringClass, strInitID);
//缓存全局引用
cachedString = (jstring) (*env).NewGlobalRef(cachedStringLocal);
(*env).DeleteLocalRef(cachedString);
}
//创建并且保存本地引用字符串
return (jstring) (*env).NewLocalRef(cachedString);
}
Push/PopLocalFrame
在管理局部引用的生命周期中,Push/PopLocalFrame 是非常方便的。你可以在 地函数的入口处调用 PushLocalFrame,然后在出口处调用 PopLocalFrame,这 样的话,在函数对中间任何位置创建的局部引用都会被释放。而且,这两个函数 是非常高效的,强烈建议使用它们。
如果你在函数的入口处调用了 PushLocalFrame,记住在所有的出口(有 return 出现的地方)调用 PopLocalFrame。在下面的代码中,对 PushLocalFrame 的调 用只有一次,但对 PopLocalFrame 的调用却需要多次。
jobject f(JNIEnv *env, ...){
jobject result;
if(*env)->PushLocalFrame(env, 10) < 0){
return NULL;//frame not pushed, no PopLocalFrame needed
}
...
result=...;
if(...){
//在返回前不要忘了调用PopLocalFrame
result= (*env).PopLocalFrame(result);
return result;
}
...
result = (*env).PopLocalFrame(result);
//正常返回
return result;
}
上面的代码同样演示了函数 PopLocalFrame 的第二个参数的用法。局部引用 result 一开始在 PushLocalFrame 创建的当前 frame 里面被创建,而把 result 传入 PopLocalFrame 中时,PopLocalFrame 在弹出当前的 frame 前,会由 result 生成一个新的局部引用,再把这个新生成的局部引用存储在上一个 frame 当中。