android 内存泄露之jni local reference table overflow (max=512)
在android项目中要实现一个需求
为了性能的要求只能用c代码来实现功能。
这样就牺牲了java跨平台性。
通过加载.so的方式,把用c实现的模块集成到app中。
android提供jni层,作为一个适配器。
可以在java层调用c接口,在jni层可以通过java提供的反射机制调用java接口和创建java对象。
最后需求完成了,自测也没问题,嘻嘻,自己也开心了一下,但是提交测试后,测试人员马上报了一个bug。
出现local reference table overflow (max=512)这样的一个错误。我去,尽然出现了崩溃。
google和百度了半天,才发现原来发生了jni层的内存泄露,导致了崩溃。
jni层到底出现了啥内存泄露????
从java代码进入jni层本地代码调用时,Dalvik就创建了一张local reference表来存储local reference,这张表
的表项数有最大值限制,一般最大为都是512个,local reference表只有当退出jni层代码的调用是才会清除掉。
当表项数超过最大值限制时,Dalvik就会抛出异常,导致了崩溃。
何为local reference????
他是在native代码层他是一个本地变量和java对象的引用。
如
JNIEXPORT jint JNICALL Java_com_print(JNIEnv *env, jobject obj){
char data[] = "daffdasf";
jstring content = (*env)->NewStringUTF(env, data);
}
变量content在 函数Java_com_print中是一个本地变量并且是String对象一个引用。所以会在local reference table
中追加一个表项来指向java对象。
其实每次进入native代码都会存在一个全局指向local reference table起始位置的ptr变量。
而上面函数中的content只是代表一个在local reference table中的偏移,通过ptr + content偏移
从local reference table获取java对象的值。
什么原因会发生local reference table overflow?????
那就是在一个循环中不断的创建local reference,而没有调用DeleteLocalRef去销毁这个local reference,
从而导致local reference table中表项不断增加,最后超过最大值,抱出了异常,导致了崩溃。
举两个例子哈:
例子1.
JNIEXPORT jint JNICALL Java_com_example(JNIEnv *env, jobject obj){
char data[] = "daffdasf";
int i = 0;
for(i = 0;i < 1000;i++){
jstring content = (*env)->NewStringUTF(env, data);
}
}
例子1代码会导致local reference table overflow
例子2.
int Java_com_example(char * data){
JNIEnv *env = NULL;
JavaVM * vm = NULL;
vm = getVm();
(*vm)->AttachCurrentThread(vm, &env, NULL);
jstring content = (*env)->NewStringUTF(env, data);
}
void callExample(){
int i = 0;
char data [] = "dafdfdasfds";
for(i = 0;i < 1000;i++){
Java_com_example(data);
}
}
例子2代码会导致local reference table overflow
大家写jni的代码时要防止jni层的内存泄露,要注意用本地语言的方式清除本地语言获得的内存,
也要注意local reference和global reference的使用。
当然jni官文文档中没有告诉我们jni层实现的细节,只告诉我们如何规范的编写jni代码,这当然是正确的做法。
对于使用者来说只需要关注的他的使用不需要关注实现细节,这样就保证了可扩展性。
所以对于不同的对local reference的实现可能结果不一样,就有可能不出现上面local reference table overflow的错误了。