1 JNI中的引用、引用类型及引用变量
JNI中的引用与java中的类似,是用来在JNI中访问java对象的,那么JNI中谁来持有引用呢,那当然是引用变量,那么引用变量如何定义呢,那当然是通过JNI定义的引用类型。JNI中也定义了与java引用类型对应的引用类型(如:jobject、jstring、jclass、jarray、jintArray等),与java类似,JNI的这些引用类型定义的变量称为引用变量,用于持有相同类型的引用,进而访问Java对象。
2 JNI引用分类及对比
JNI根据引用的生命周期和作用范围不同,将引用分为三种,分别是 局部引用、全局引用、弱全局引用,三者区别具体如下:
局部引用 | 全局引用 | 弱全局引用 | |
作用域 | 方法内部 | 工程内部 | 工程内部 |
生命周期 | 开始:手动创建 / JNI接口返回 结束:手动释放 / 方法结束 | 开始:手动创建 结束:手动释放 | 开始:手动创建 结束:手动释放 / 被GC回收 |
生命周期内是否会被GC回收 | × | × | √ (若JNI中java对象只有弱全局引用,则随时会被GC回收) |
能否跨方法使用 | × | √ | √ |
能否跨线程使用 | × | √ | √ |
存储位置 | 局部引用表 | 全局引用表 | 弱全局引用表 |
上限 (跟Android版本有关,以android10 为例) | 一个应用中 JNI最多可创建8388608个局部引用 若超过则报错table overflow | 一个应用中 JNI最多可创建51200个全局引用 若超过则报错table overflow | 一个应用中 JNI最多可创建51200个弱全局引用 若超过则报错table overflow |
是否有下限 | JNI每个线程中默认至少可创建16个局部引用 | 无 | 无 |
注意:应用开始到结束过程中,JNI会将过程中所产生的局部引用、全局引用、弱全局引用分别存储在 局部引用表、全局引用表、弱全局引用表中,上面说的上限,是整个JNI层创建引用的上限,也是各个表的长度,如果应用执行的过程中,JNI创建的引用超过了表的长度,JNI就会报错 table overflow,所以我们应该及时释放用不到的局部、全局、弱全局引用。
注意:在 android 8.0 以下版本,局部引用上限为512。
3 JNI引用详解
(1)局部引用
在JNI中,局部引用比较常见,返回值为对象引用的JNI接口,所返回的引用基本都是局部引用;局部引用会阻止GC回收该引用所引用的对象;局部引用不能跨方法、跨线程使用。
1)涉及到的JNI接口
功能 | 接口 |
手动创建局部引用 | NewLocalRef |
销毁局部引用 | DeleteLocalRef |
申请当前线程至少可用的局部引用数量 | EnsureLocalCapacity |
为函数创建一个指定数量的局部引用帧 | PushLocalFrame |
释放局部引用帧中的引用 | PopLocalFrame |
2)接口详解
/**
* 作用:创建一个局部引用
* @param ref 可以是局部引用、全局引用、弱全局引用。可以传null
* @return 相应对象的局部引用。若 内存溢出 或 ref为null 或 ref是已经被回收的弱全局引用 则 返回null
*
* @exceptions 不报异常
*/
jobject NewLocalRef(jobject ref);
/**
* 作用:释放一个局部引用
* @param localRef 只能是局部引用 或 null。
*
* @exceptions 不报异常
*
* 注意:同一个非空局部引用不能删除两次
*/
void DeleteLocalRef(jobject localRef);
/**
* 作用:申请当前线程至少可用的局部引用数量
* @param capacity 当前线程 至少可用的局部引用数量
* @return
* 0:申请成功
* 负数:申请失败并抛出OutOfMemoryError异常
*
* @exceptions
* OutOfMemoryError: 若系统空间不足,则报该异常
*
* 注意:JNI中每个线程至少可创建16个局部引用
*/
jint EnsureLocalCapacity(jint capacity);
/**
* 作用:为函数创建一个指定大小的局部引用帧(一段栈上的连续的存储空间),与PopLocalFrame成对使用,
* 在二者之间创建的局部引用会被存储在其中
* @param capacity 帧的大小
* @return
* 0:创建成功
* 负数:创建失败,并抛出OutOfMemoryError异常
*
* @exceptions
* OutOfMemoryError: 若系统空间不足,则报该异常
*
* 注意:创建的局部引用帧仅供当前函数使用(即调用这个接口的函数)
*/
jint PushLocalFrame(jint capacity);
/**
* 作用:清空使用PushLocalFrame接口创建的局部引用帧中的所有局部引用或只保留一个,与PushLocalFrame成对使用
* @param result 传入null,则释放帧中的全部局部引用;传入局部引用,则释放除result之外的其他局部引用
* @return 需要保留的局部引用
*
* 注意:返回的局部引用并不是将帧中要保留的局部引用直接返回,而是将帧中的所有引用全部释放后,重新创建一个相同的引用
*/
jobject PopLocalFrame(jobject result);
3)接口使用
主要对局部引用的创建和释放的方法进行示例说明。当JNI函数不会创建太多局部引用,且不会阻塞 或 耗时 时,不必手动处理局部引用,用方法1即可;当JNI函数需要创建较多引用,且函数短时间内不会结束时,应该用方法2;在方法2的情况下,如果嫌一个一个释放太累,可以使用方法3批量手动释放局部引用。
为了养成用完就释放的好习惯,什么情况下都手动释放局部引用~
//局部引用的创建及释放
//方法1:自动创建与释放
void localRef1(JNIEnv * env){
//1 创建引用变量
jstring jstr;
//2 通过JNI接口得到局部引用,并赋给引用变量
jstr = env->NewStringUTF("uuuu");
//3 方法结束自动释放局部引用
return;
}
//方法2:用 NewLocalRef/DeleteLocalRef 手动创建与释放
void localRef2(JNIEnv * env){
//1 创建引用变量
jstring jstr;
//2 手动创建局部引用,需要强制类型转换一下
jstr = (jstring)env->NewLocalRef(env->NewStringUTF("uuuu"));
//3 手动释放局部引用
env->DeleteLocalRef(jstr);
}
//方法3:用 PushLocalFrame/PopLocalFrame 手动创建与释放
//(1)局部引用帧无保留
void localRef3(JNIEnv * env){
//1 创建局部引用帧
env->PushLocalFrame(20);
//2 jni接口返回局部引用
jstring jstr1 = env->NewStringUTF("uuuu");
jstring jstr2 = env->NewStringUTF("iiii");
jstring jstr3 = env->NewStringUTF("tttt");
//3 手动释放局部引用帧中的所有局部引用
env->PopLocalFrame(nullptr);
}
//(2)局部引用帧有保留
jstring localRef4(JNIEnv * env){
//1 创建局部引用帧
env->PushLocalFrame(20);
//2 jni接口返回局部引用
jstring jstr1 = env->NewStringUTF("uuuu");
jstring jstr2 = env->NewStringUTF("iiii");
jstring jstr3 = env->NewStringUTF("tttt");
//3 手动释放局部引用帧中除jstr3的局部引用
return (jstring)env->PopLocalFrame(jstr3);
}
(2)全局引用
1)涉及到的JNI接口
功能 | 接口 |
创建全局引用 | NewGlobalRef |
销毁全局引用 | DeleteGlobalRef |
2)接口详解
/**
* 作用:创建全局引用
* @param obj 可以是 局部引用、全局引用、弱全局引用、null
* @return 返回 全局引用 或 null(传入null 或 已被回收的弱全局引用)
*
* @exceptions 不报异常
*/
jobject NewGlobalRef(jobject obj);
/**
* 作用:释放全局引用
* @param globalRef 全局引用 或 null
*
* @exceptions 不报异常
*/
void DeleteGlobalRef(jobject globalRef);
3)接口使用
//全局引用的创建及释放
//1 定义引用变量(在方法外定义,便于全局使用)
jstring jstr_global;
void GlobalRef(JNIEnv * env){
//2 获取局部引用
jstring jstr = env->NewStringUTF("uuuu");
//3 创建全局引用
jstr_global = (jstring)env->NewGlobalRef(jstr);
//4 释放全局引用
env->DeleteGlobalRef(jstr_global);
}
(3)弱全局引用
1)涉及到的JNI接口
功能 | 接口 |
创建弱全局引用 | NewWeakGlobalRef |
销毁弱全局引用 | DeleteWeakGlobalRef |
判断两个对象是否一样,在这里用于判断弱全局引用是否为null | IsSameObject |
2)接口详解
/**
* 作用:创建弱全局引用
* @param obj 局部引用、全局引用、弱全局引用
* @return 若obj非null,则返回弱全局引用;若obj为null,则返回null
*
* @exceptions
* OutOfMemoryError:内存不足时报该异常
*/
jweak NewWeakGlobalRef(jobject obj);
/**
* 作用:释放弱全局引用
* @param obj 必须是弱全局引用
*
* @exceptions 不报异常
*
* 注意:同一个弱全局引用不能删除两次
*/
void DeleteWeakGlobalRef(jweak obj);
3)接口使用
//弱全局引用的创建、使用及释放
//1 定义引用变量(在方法外定义,便于全局使用)
jstring jstr_weak_global;
void WeakGlobalRef(JNIEnv * env){
//2 获取局部引用
jstring jstr = env->NewStringUTF("uuuu");
//3 创建弱全局引用
jstr_weak_global = (jstring)env->NewWeakGlobalRef(jstr);
//4 使用弱全局引用之前判断一下 是否已经被回收
if(!env->IsSameObject(jstr_weak_global, nullptr)){
//TODO:你的处理逻辑
}
//5 释放弱全局引用
env->DeleteWeakGlobalRef(jstr_global);
}
注意:由于只有弱全局引用的java对象随时会被回收,所以使用弱全局引用时,要用IsSameObject判断一下,弱全局引用是否为null。
(4)其他相关接口
1)涉及到的JNI接口
功能 | 接口 |
获取引用类型 | GetObjectRefType |
2)接口详解
/**
* 作用:获取引用类型
* @param obj 引用
* @return 返回枚举值,代表相应的引用类型
*
* 注意:该接口在JNI_1.6版本中添加,低版本并没有
*/
jobjectRefType GetObjectRefType(jobject obj);
其中返回值 jobjectRefType 是定义在jni.h中的枚举:
typedef enum jobjectRefType {
JNIInvalidRefType = 0,
JNILocalRefType = 1,
JNIGlobalRefType = 2,
JNIWeakGlobalRefType = 3
} jobjectRefType;
3)接口使用
//例子:释放引用
void releaseRef(JNIEnv * env,jobject jobj){
//获取引用类型
jobjectRefType type = env->GetObjectRefType(jobj);
if(type == JNILocalRefType){
env->DeleteLocalRef(jobj);
}else if(type == JNIGlobalRefType){
env->DeleteGlobalRef(jobj);
}else if(type == JNIWeakGlobalRefType){
env->DeleteWeakGlobalRef(jobj);
}
}
(5)小结
** 所有引用使用完,最好都手动释放调。对于尤其是循环中使用的局部引用。
** 释放引用 仅仅是使引用不可达JNI中创建的java对象,并清理该引用,并不是释放java对象,java对象的回收释放需要GC才行,把对象的引用释放掉,只是让对象可以被回收,对象具体什么时候被回收还要看GC,这点跟java是相同的。
** 局部引用不能跨线程、跨方法,不要将局部引用赋给全局引用变量。
** JNI层的引用都是有上限的,局部引用Android8.0以上上限8388608个,Android8.0以下,上限512个;全局引用android8.0以上,上限51200个;弱全局引用android8.0以上,上限51200个;若JNI层创建的引用超过上限,则报错,程序退出。
** 使用弱全局引用时,先使用IsSameObject接口判断是否为null。GetObjectRefType。
** GetObjectRefType是JNI_1.6版本开始添加的,低版本使用不了。