2.4.7 垃圾回收
最后更新于:2022-04-02 05:48:27
我们知道,Java中创建的对象最后是由垃圾回收器来回收和释放内存的,可它对JNI有什么影响呢?下面看一个例子:
**垃圾回收例子**
~~~
static jobject save_thiz = NULL; //定义一个全局的jobject
static void
android_media_MediaScanner_processFile(JNIEnv*env, jobject thiz, jstring path,
jstringmimeType, jobject client)
{
......
//保存Java层传入的jobject对象,代表MediaScanner对象
save_thiz = thiz;
......
return;
}
//假设在某个时间,有地方调用callMediaScanner函数
void callMediaScanner()
{
//在这个函数中操作save_thiz,会有问题吗?
}
~~~
上面的做法肯定会有问题,因为和save_thiz对应的Java层中的MediaScanner很有可能已经被垃圾回收了,也就是说,save_thiz保存的这个jobject可能是一个野指针,如使用它,后果会很严重。
可能有人要问,将一个引用类型进行赋值操作,它的引用计数不会增加吗?而垃圾回收机制只会保证那些没有被引用的对象才会被清理。问得对,但如果在JNI层使用下面这样的语句,是不会增加引用计数的。
~~~
save_thiz = thiz; //这种赋值不会增加jobject的引用计数。
~~~
那该怎么办?不必担心,JNI规范已很好地解决了这一问题,JNI技术一共提供了三种类型的引用,它们分别是:
- Local Reference:本地引用。在JNI层函数中使用的非全局引用对象都是Local Reference。它包括函数调用时传入的jobject、在JNI层函数中创建的jobject。LocalReference最大的特点就是,一旦JNI层函数返回,这些jobject就可能被垃圾回收。
- Global Reference:全局引用,这种对象如不主动释放,就永远不会被垃圾回收。
- Weak Global Reference:弱全局引用,一种特殊的GlobalReference,在运行过程中可能会被垃圾回收。所以在程序中使用它之前,需要调用JNIEnv的IsSameObject判断它是不是被回收了。
平时用得最多的是Local Reference和Global Reference,下面看一个实例,代码如下所示:
**android_media_MediaScanner.cpp::MyMediaScannerClient构造函数**
~~~
MyMediaScannerClient(JNIEnv *env, jobjectclient)
: mEnv(env),
//调用NewGlobalRef创建一个GlobalReference,这样mClient就不用担心被回收了。
mClient(env->NewGlobalRef(client)),
mScanFileMethodID(0),
mHandleStringTagMethodID(0),
mSetMimeTypeMethodID(0)
{
......
}
//析构函数
virtual ~MyMediaScannerClient()
{
mEnv->DeleteGlobalRef(mClient);//调用DeleteGlobalRef释放这个全局引用。
}
~~~
每当JNI层想要保存Java层中的某个对象时,就可以使用Global Reference,使用完后记住释放它就可以了。这一点很容易理解。下面要讲有关LocalReference的一个问题,还是先看实例,代码如下所示:
**android_media_MediaScanner.cpp::MyMediaScannerClient的scanFile**
~~~
virtualbool scanFile(const char* path, long long lastModified,
long long fileSize)
{
jstringpathStr;
//调用NewStringUTF创建一个jstring对象,它是Local Reference类型。
if((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;
//调用Java的scanFile函数,把这个jstring传进去
mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr,lastModified, fileSize);
/*
根据LocalReference的说明,这个函数返回后,pathStr对象就会被回收。所以
下面这个DeleteLocalRef调用看起来是多余的,其实不然,这里解释一下原因:
1)如果不调用DeleteLocalRef,pathStr将在函数返回后被回收。
2)如果调用DeleteLocalRef的话,pathStr会立即被回收。这两者看起来没什么区别,
不过代码要是像下面这样的话,虚拟机的内存就会被很快被耗尽:
for(inti = 0; i < 100; i++)
{
jstring pathStr = mEnv->NewStringUTF(path);
......//做一些操作
//mEnv->DeleteLocalRef(pathStr); //不立即释放Local Reference
}
如果在上面代码的循环中不调用DeleteLocalRef的话,则会创建100个jstring,
那么内存的耗费就非常可观了!
*/
mEnv->DeleteLocalRef(pathStr);
return(!mEnv->ExceptionCheck());
}
~~~
所以,没有及时回收的Local Reference或许是进程占用过多的一个原因,请务必注意这一点。
';