在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的錯誤了。