下例以FindClass返回的引用,存储于全局变量中,或静态变量中。
第一次运行,创建,正常,
第二次之后,使用存储于全局变量的引用去操作,结果报异常
#include
#include
//
// Created by luohy on 2018/9/4.
//
/* This code is illegal */
jclass stringClass = NULL;
extern "C"
JNIEXPORT jstring JNICALL
Java_com_lhy_comm_test_JNITest_MyNewString(
JNIEnv* env,
jobject /* this */) {
/* This code is illegal */
//static jclass stringClass = NULL;
jmethodID cid;
jcharArray elemArr;
jstring result;
if (stringClass == NULL) {
stringClass = (env)->FindClass("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(stringClass, "", "([C)V");
elemArr = (env)->NewCharArray( 10);
result = (jstring)(env)->NewObject(stringClass, cid, elemArr);
(env)->DeleteLocalRef( elemArr);
return result;
}
这种保存局部引用到全局或静态变量中的方式是不正确的, 因为FindClass()返回的是对java.lang.String的局部引用。
在native代码从MyNewString返回退出时,VM 会释放所有局部引用,包括存储在stringClass变量中的指向类对象的引用。
这样当后继再次调用MyNewString时,直接使用stringClass变量去操作,可能会访问非法地址,导致内存被破坏,或者系统崩溃。局部引用失效,有两种方式:‘ 1)系统会自动释放局部变量。
2)程序员可以显示地管理局部引用的生命周期,例如调用DeleteLocalRef。局部对象只属于创建它们的线程,只在该线程中有效。 一个线程想要调用另一个线程创建的局部引用是不被允许的。
将一个局部引用保存到全局变量中,然后在其它线程中使用它,这是一种错误的编程。
和局部引用不一样(局部变量可以由多数JNI函数创建),全局引用只能由一个JNI函数创建(NewGlobalRef)。
/* This code is OK */
extern "C"
JNIEXPORT jstring JNICALL
Java_com_lhy_comm_test_JNITest_MyNewGlobalString(
JNIEnv* env, jclass clazz)
{
static jclass stringClass = NULL;
if (stringClass == NULL) {
jclass localRefCls =
(jclass)(env)->FindClass( "java/lang/String");
if (localRefCls == NULL) {
return NULL; /* exception thrown */
}
/* Create a global reference */
stringClass = (jclass)(env)->NewGlobalRef( localRefCls);
/* The local reference is no longer useful */
(env)->DeleteLocalRef( localRefCls);
/* Is the global reference created successfully? */
if (stringClass == NULL) {
return NULL; /* out of memory exception thrown */
}
}
jmethodID cid;
jcharArray elemArr;
jstring result;
cid = (env)->GetMethodID(stringClass, "", "([C)V");
elemArr = (env)->NewCharArray( 10);
result = (jstring)(env)->NewObject(stringClass, cid, elemArr);
(env)->DeleteLocalRef( elemArr);
return result;
}
一个全局引用可以跨越多个线程 并且保证了所引用的对象不会被垃圾回收,在被程序员释放之前,一致有效 (因此,小心内存泄露哦)
所有的native方法均可以访问全局引用变量
弱全局引用是在java 2 SDK1.2才出现的。
它由NewGolableWeakRef函数创建,并且被DeleteGloablWeakRef函数摧毁。
和全局引用一样,它可以跨native方法调用,也可以跨越不同线程。
但是和全局引用不同的是,它不阻止对基础对象的垃圾回收。
/* This code is OK */
extern "C"
JNIEXPORT jstring JNICALL
Java_com_lhy_comm_test_JNITest_MyNewWeakGlobalRefString(
JNIEnv* env, jclass clazz)
{
static jclass stringClass = NULL;
if (stringClass == NULL) {
jclass localRefCls =
(jclass)(env)->FindClass( "java/lang/String");
if (localRefCls == NULL) {
return NULL; /* exception thrown */
}
/* Create a global reference */
stringClass = (jclass)(env)->NewWeakGlobalRef( localRefCls);
/* The local reference is no longer useful */
(env)->DeleteLocalRef( localRefCls);
/* Is the global reference created successfully? */
if (stringClass == NULL) {
return NULL; /* out of memory exception thrown */
}
}
jmethodID cid;
jcharArray elemArr;
jstring result;
cid = (env)->GetMethodID(stringClass, "", "([C)V");
elemArr = (env)->NewCharArray( 10);
result = (jstring)(env)->NewObject(stringClass, cid, elemArr);
(env)->DeleteLocalRef( elemArr);
return result;
}
弱全局引用在一个被native代码缓存着的引用不想阻止基础对象被垃圾回收时,非常有用。
如以上例子,假设是使用mypkg.MyCls2,那么通过将mypkg.MyCls2缓存到弱引用中,能够实现MyCls2类依旧可以被卸载。
因此,一般还需要在使用前判断引用是否有效,方法见下。
可以用JNI函数IsSameObject来检查给定的两个局部引用、全局引用或者弱全局引用,是否指向同一个对象。
在java VM中NULL是null的引用。
如果一个对象obj是局部引用或者全局引用,则可以这样来检查它是否指向null对象:
(*env)->IsSameObject(env, obj, NULL) 或者 NULL == obj
而对于弱全局引用,以上规则需要改变一下:
我们可以用这个函数来判断一个非0弱全局引用wobj所指向的对象是否仍旧存活着(依旧有效)。
(*env)->IsSameObject(env, wobj, NULL)
除了引用的对象要占用内存,每个JNI引用本身也会消耗一定内存。
作为一个JNI程序员,应该对在一段给定的时间里,程序会用到的引用的个数,做到心中有数。
特别是,尽管程序所创建的局部引用最终会被VM会被自动地释放,仍旧需要知道在程序在执行期间的任何时刻,创建的局部引用的上限个数。
创建过多的引用,即便他们是瞬间、短暂的,也会导致内存耗尽。
for (i = 0; i < len; i++) {
jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
... /* process jstr */
(*env)->DeleteLocalRef(env, jstr);
}
2)你可能要创建一个工具函数,它会被未知的上下文调用。
例如之前提到到MyNewString这个例子,它在每次返回调用者欠,都及时地将局部引用释放。
3)native方法,可能不会返回(例如,一个可能进入无限事件分发的循环中的方法)。
此时在循环中释放局部引用,是至关重要的,这样才能不会无限期地累积,进而导致内存泄露。
4)native方法可能访问一个巨大的对象,因此,创建了一个指向该对象的局部引用。
native方法在返回调用者之前,除访问对象之外,还执行了额外的计算。指向这个大对象的局部引用,将会包含该对象,以防被垃圾回收。
这个现象会持续到native方法返回到调用者时,即便这个对象不会再被使用,也依旧会受保护。
在以下例子中,由于在lengthyComputation()前,显示地调用了DeleteLocalRef,所以垃圾回收器有机会可以释放lref所指向的对象。
/* A native method implementation */
JNIEXPORT void JNICALL
Java_pkg_Cls_func(JNIEnv *env, jobject this)
{
lref = ... /* a large Java object */
... /* last use of lref */
(*env)->DeleteLocalRef(env, lref);
lengthyComputation(); /* may take some time */
return; /* all local refs are freed */
}
这个情形的实质,就是允许程序在native方法执行期间,java的垃圾回收机制有机会回收native代码不在访问的对象。
在java 2 SDK1.2中管理局部引用
不知道java 7怎么样了,应该更强大吧,有时间,去看看,这里且按照java2的特性来吧。
SDK1.2中提供了一组额外的函数来管理局部引用的生命周期。他们是EnsureLocalCapacity、NewLocalRef、PushLocalFram以及PopLocalFram。
JNI的规范要求VM可以自动确保每个native方法可以创建至少16个局部引用。
经验显示,如果native方法中未包含和java VM的对象进行复杂的互相操作,这个容量对大多数native方法而言,已经足够了。
如果,出现这还不够的情况,需要创建更多的局部引用,那么native方法可以调用EnsureLocalCapacity来保证这些局部引用有足够的空间。
未完待续… …
https://blog.csdn.net/huguohu2006/article/details/7397055