简介
- 通常不必关心内部对象释放内存问题,因为已经交由 Java虚拟机 负责。
-
JNI 支持3中不透明引用: 局部引用 、 全局引用 、 弱全局引用 。
局部引用
简介
-
局部引用 可以由一系列 JNI函数 获取。
-
局部引用 会在原生函数返回时被自动释放。
- 只能在创建了 局部引用 的线程使用此 局部引用 ,不该使用全局变量存储 局部引用 指望别的线程能使用此引用。
- 不要使用 静态存储生命周期变量 (即全局变量,局部静态变量)存储 局部引用 ,然后指望下次使用此静态变量时, 局部引用 仍然有效。
-
局部引用 可以通过
DeleteLocalRef()
手动释放。
手动释放局部引用
- 通常情况下不必手动释放 局部引用 ,但是在以下情况则需要手动释放:
- 创建了大量的 局部引用 ,如数组元素等,可以每用完一个释放一个。
- 这个 局部引用 是在 效用函数 中创建的。
- 这个 局部引用 指向一个巨大的对象。
- 原生函数永远都不会返回,比如一个永久循环。
管理局部引用
函数解说
-
EnsureLocalCapacity()
:用于确保有足够的空间给某个数量的 局部引用 。
-
PushLocalFrame() / PopLocalFrame()
:
- 开始一段区域,在此区域创建的 局部引用 都会被记录,在随后调用
PopLocalFrame()
时会释放这段区域创建的所有 局部引用 。
-
PushLocalFrame() / PopLocalFrame()
十分有效率,可以在进入 效用函数 时调用PushLocalFrame()
,在return
的地方调用PopLocalFrame()
。若有多个return
的时候,每个return
处都需要调用PopLocalFrame()
。
-
PopLocalFrame()
的第二个参数十分有用,在碰见需要释放所有 局部引用 除了其中一个保留并返回时,可以将此引用传递PopLocalFrame()
第二个参数。
-
NewLocalRef()
:用于在 效用函数 中返回一个 局部引用 。
EnsureLocalCapacity()
示例
// 开始获取局部引用前先确保有足够的空间
if (((* env)->EnsureLocalCapacity(env, len)) < 0) {
// 内存不足
}
for (int i = 0; i < len; i++) {
jstring jstr = (* env)->GetObjectArrayElement(env, arr, i);
}
PushLocalFrame() / PopLocalFrame()
示例
jobject f(JNIEnv * env, ...) {
jobject result = NULL;
// 开始一段区域,并保证有足够的空间
if ((*env)->PushLocalFrame(env, 10) < 0) {
return NULL;
}
// ...
result = ...;
if (...) {
// 结束这段区域,区域中获取的局部引用全部释放,result通过传入传出得到了保留
result = (* env)->PopLocalFrame(env, result);
return result;
}
// 保证所有的return前都调用了PopLocalFrame
result = (* env)->PopLocalFrame(env, result);
return result;
}
NewLocalRef()
示例
jstring MyNewString(JNIEnv * env, jchar * chars, jint len) {
static jstring result;
if (wstrncmp("CommonString", chars, len) == 0) {
static jstring cachedString = NULL;
if (cachedString == NULL) {
jstring cachedStringLocal = ...;
cachedString = (* env)->NewGlobalRef(env, cachedStringLocal);
}
// 将全局引用cachedString转换为局部引用后返回
return (* env)->NewLocalRef(env, cachedString);
}
return result;
}
全局引用
- 和 局部引用 不一样, 全局引用 只需要一个 JNI函数 便能获取,即
NewGlobalRef()
。
- 可以使用 全局引用 在多个函数调用间或多个线程间共享。
-
全局引用 不会被自动释放,需要手动使用
DeleteGlobalRef()
释放。
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;
}
// 通过局部引用来获取全局引用
stringClass = (* env)->NewGlobalRef(env, localRefCls);
// 先释放局部引用,再检查是否成功获取全局引用
// 因为无论是否成功,局部引用都不再有效
(* env)->DeleteLocalRef(env, localRefCls);
if (stringClass == NULL) {
return NULL;
}
}
// ...
}
弱全局引用
-
弱全局引用 使用
NewWeakGlobalRef()
来获取。
-
弱全局引用 和 全局引用 一样,可以在多个函数调用间或多个线程间共享。
-
弱全局引用 可能会被垃圾回收器回收。
-
弱全局引用 虽然可能会被自动释放,但仍然需要使用
DeleteWeakGlobalRef()
来手动释放。
- 像
java.lang.String
这样的系统类永远不会被垃圾回收器回收。
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;
}
// 使用局部引用获取弱全局引用
myCls2 = (* env)->NewWeakGlobalRef(env, myCls2Local);
if (myCls2 == NULL) {
return;
}
}
// ...
}
比较引用
- 可以使用
IsSameObject()
来检查两个引用(局部、全局或弱全局)是否指向同一个对象。如果返回JNI_TRUE
,则两个引用指向同一个对象。如果返回JNI_FALSE
,则相反。
- 若引用的值为
NULL
,则此引用指向 Java 中的null
对象。
- 对于 局部引用 和 全局引用 可以通过
(* env)->IsSameObject(env, obj, NULL)
来判断引用是否指向null
对象。若返回JNI_TRUE
则指向null
对象。
- 对于 弱全局引用 可以通过
(* env)->IsSameObject(env, wobj, NULL)
来检查到更多。若返回JNI_TRUE
,则表明引用要么指向null
对象,要么被垃圾回收器回收了。无论哪一种,引用都不能再使用了。
管理引用的规则
-
JNI 代码中有三种类型的函数:
-
JNI函数 : JNI 的 API 函数。
-
原生函数 :实现 Java 代码中 native方法声明 的 C 函数。
-
效用函数 : C 工具函数。
- 对于 原生函数 ,应该注意:
- 避免大量创建 局部引用 。
- 避免不返回的 原生函数 创建了不必要的 局部引用 。
- 对于 效用函数 ,应该注意:
- 尽量避免内存泄漏。
- 如果此 效用函数 返回 原始类型 ,应该释放所有的 局部引用 。
- 如果此 效用函数 返回 引用类型 ,应该释放除了要返回的 引用类型 以外所有的 局部引用 。
- 不应该偶尔返回 局部引用 ,偶尔返回 全局引用 ,因为调用此 效用函数 的调用者需要知道返回的 引用类型 ,以便管理这个引用。
- 可以使用
NewLocalRef()
来保证 效用函数 返回的 引用类型 总是 局部引用 。
while (JNI_TRUE) {
// GetInfoString是一个效用函数,infoString是由此效用函数创建的
jstring infoString = GetInfoString(info);
// 使用infoString
// 使用完毕后,在此处需要根据infoString的引用类型(局部引用、全局引用或弱全局引用)来释放infoString。
}