解析Java的JNI编程中的对象引用与内存泄漏问题
|
JNI,Java Native Interface,是 native code 的编程接口。JNI 使 Java 代码程序可以与 native code 交互――在 Java 程序中调用 native code;在 native code 中嵌入 Java 虚拟机调用 Java 的代码。 局部和全局引用 JNI将实例、数组类型暴露为不透明的引用。native代码从不会直接检查一个不透明的引用指针的上下文,而是通过使用JNI函数来访问由不透明的引用所指向的数据结构。因为只处理不透明的引用,这样就不需要担心不同的java VM实现而导致的不同的内部对象的布局。然而,还是有必要了解一下JNI中不同种类的引用: 那么到底什么是局部引用,什么事全局引用,它们有什么不同? 局部引用 多数JNI函数都创建局部引用。例如JNI函数NewObject创建一个实例,并且返回一个指向该实例的局部引用。 局部引用只在创建它的native方法的动态上下文中有效,并且只在native方法的一次调用中有效。所有局部引用只在一个native方法的执行期间有效,在该方法返回时,它就被回收。 在native方法中使用一个静态变量来保存一个局部引用,以便在随后的调用中使用该局部引用,这种方式是行不通的。例如以下例子,误用了局部引用:
MyNewString(JNIEnv *env,jchar *chars,jint len)
{
static jclass stringClass = NULL;
jmethodID cid;
jcharArray elemArr;
jstring result;
if (stringClass == NULL) {
stringClass = (*env)->FindClass(env,"java/lang/String");
if (stringClass == NULL) {
return NULL; /* exception thrown */
}
}
/* It is wrong to use the cached stringClass here,because it may be invalid. */
cid = (*env)->GetMethodID(env,stringClass,"<init>","([C)V");
...
elemArr = (*env)->NewCharArray(env,len);
...
result = (*env)->NewObject(env,cid,elemArr);
(*env)->DeleteLocalRef(env,elemArr);
return result;
}
这种保存局部引用的方式是不正确的,因为FindClass()返回的是对java.lang.String的局部引用。这是因为,在native代码从MyNewString返回退出时,VM 会释放所有局部引用,包括存储在stringClass变量中的指向类对象的引用。这样当再次后继调用MyNewString时,可能会访问非法地址,导致内存被破坏,或者系统崩溃。 局部引用失效,有两种方式:‘ 一个局部引用可能在被摧毁之前,被传给多个native方法。例如,MyNewString中,返回一个由NewObject创建的字符串引用,它将由NewObject的调用者来决定是否释放该引用。而在以下代码中:
JNIEXPORT jstring JNICALL Java_C_f(JNIEnv *env,jobject this) {
char *c_str = ...<pre name="code" class="cpp"> ... <pre name="code" class="cpp">return MyNewString(c_str);<pre name="code" class="cpp">}
在VM接收到来自Java_C_f的局部引用以后,将基础字符串对象传递给ava_C_f的调用者,然后摧毁原本由MyNewString中调用的JNI函数NewObject所创建的局部引用。 局部对象只属于创建它们的线程,只在该线程中有效。一个线程想要调用另一个线程创建的局部引用是不被允许的。将一个局部引用保存到全局变量中,然后在其它线程中使用它,这是一种错误的编程。 全局引用 在一个native方法被多次调用之间,可以使用一个全局引用跨越它们。一个全局引用可以跨越多个线程,并且在被程序员释放之前,一致有效。和局部引用一样,全局引用保证了所引用的对象不会被垃圾回收。 和局部引用不一样(局部变量可以由多数JNI函数创建),全局引用只能由一个JNI函数创建(NewGlobalRef)。下面是一个使用全局引用版本的MyNewString:
MyNewString(JNIEnv *env,jint len)
{
static jclass stringClass = NULL;
...
if (stringClass == NULL) {
jclass localRefCls =
(*env)->FindClass(env,"java/lang/String");
if (localRefCls == NULL) {
return NULL; /* exception thrown */
}
/* Create a global reference */
stringClass = (*env)->NewGlobalRef(env,localRefCls);
/* The local reference is no longer useful */
(*env)->DeleteLocalRef(env,localRefCls);
/* Is the global reference created successfully? */
if (stringClass == NULL) {
return NULL; /* out of memory exception thrown */
}
}
...
}
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; /* can't find class */
}
myCls2 = NewWeakGlobalRef(env,myCls2Local);
if (myCls2 == NULL) {
return; /* out of memory */
}
}
... /* use myCls2 */
}
弱全局引用在一个被native代码缓存着的引用不想阻止基础对象被垃圾回收时,非常有用。如以上例子,mypkg.MyCls.f需要缓存mypkg.MyCls2的引用。而通过将mypkg.MyCls2缓存到弱引用中,能够实现MyCls2类依旧可以被卸载。
比较引用 可以用JNI函数IsSameObject来检查给定的两个局部引用、全局引用或者弱全局引用,是否指向同一个对象。
(*env)->IsSameObject(env,obj,NULL) 或者: NULL == obj
(*env)->IsSameObject(env,wobj,NULL) 返回值: 释放引用 释放局部引用
for (i = 0; i < len; i++) {
jstring jstr = (*env)->GetObjectArrayElement(env,arr,i);
... /* process jstr */
(*env)->DeleteLocalRef(env,jstr);
}
2)你可能要创建一个工具函数,它会被未知的上下文调用。例如之前提到到MyNewString这个例子,它在每次返回调用者欠,都及时地将局部引用释放。
(编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- java – MVC和MVC Model2之间的实际区别是什么?
- java – 为什么“mvn assembly:single”只用程序集创建一个
- Java concurrency之Condition条件_动力节点Java学院整理
- 使用事件侦听器作为Java 8 Stream源
- SpringBoot中的Thymeleaf用法
- Spring定时任务中@PostConstruct被多次执行异常的分析与解决
- Springboot项目如何使用apollo配置中心
- java通过request自动封装复杂对象
- 任何人都可以解释一下java设计HashMap的hash()函数吗?
- Android 平台创建 XY 图表的完整例子
