2.4.7 垃圾回收

我们知道,Java中创建的对象最后是由垃圾回收器来回收和释放内存的,可它对JNI有什么影响呢?下面看一个例子:


[—>垃圾回收的例子]

static jobject save_thiz=NULL;//定义一个全局的jobject

static void

android_media_MediaScanner_processFile(JNIEnv*env,jobject thiz,jstring path,jstring mimeType,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的引用计数。


引用计数没有增加,thiz就有可能被回收,那该怎么办?不必担心,JNI规范已很好地解决了这一问题,JNI技术一共提供了三种类型的引用,它们分别是:

Local Reference:本地引用。在JNI层函数中使用的非全局引用对象都是Local Reference,它包括函数调用时传入的jobject和在JNI层函数中创建的jobject。Local Reference最大的特点就是,一旦JNI层函数返回,这些jobject就可能被垃圾回收。

Global Reference:全局引用,这种对象如不主动释放,它永远不会被垃圾回收。

Weak Global Reference:弱全局引用,一种特殊的Global Reference,在运行过程中可能会被垃圾回收。所以在使用它之前,需要调用JNIEnv的IsSameObject判断它是否被回收了。

平时用得最多的是Local Reference和Global Reference,下面来看一个实例,代码如下所示:


[—>android_media_MediaScanner.cpp:MyMediaScannerClient构造函数]

MyMediaScannerClient(JNIEnv*env,jobject client)

:mEnv(env),

//调用NewGlobalRef创建一个Global Reference,这样mClient就不用担心被回收了。

mClient(env->NewGlobalRef(client)),

mScanFileMethodID(0),

mHandleStringTagMethodID(0),

mSetMimeTypeMethodID(0)

{

……

}

//析构函数

virtual~MyMediaScannerClient()

{

mEnv->DeleteGlobalRef(mClient);//调用DeleteGlobalRef释放这个全局引用。

}


每当JNI层想要保存Java层中的某个对象时,就可以使用Global Reference,使用完后记住释放它就可以了。这一点很容易理解。下面要讲有关Local Reference的一个问题,还是先看实例,代码如下所示:


[—>android_media_MediaScanner.cpp:MyMediaScannerClient的scanFile]

virtual bool scanFile(const char*path,long long lastModified,long long fileSize)

{

jstring pathStr;

//调用NewStringUTF创建一个jstring对象,它是Local Reference类型。

if((pathStr=mEnv->NewStringUTF(path))==NULL)return false;

//调用Java的scanFile函数,把这个jstring传进去

mEnv->CallVoidMethod(mClient,mScanFileMethodID,pathStr,

lastModified,fileSize);

/*

根据Local Reference的说明,这个函数返回后,pathStr对象就会被回收。所以下面这个DeleteLocalRef调用看起来是多余的,其实不然,这里解释一下原因:

1)如果不调用DeleteLocalRef,pathStr将在函数返回后被回收。

2)如果调用DeleteLocalRef,pathStr会立即被回收。这两者看起来没什么区别,不过代码如果像下面这样,虚拟机的内存就会很快被耗尽:

for(int i=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或许是进程占用内存过多的一个原因,请务必注意这一点。