在android項目中要實現一個需求java
爲了性能的要求只能用c代碼來實現功能。android
這樣就犧牲了java跨平臺性。app
經過加載.so的方式,把用c實現的模塊集成到app中。函數
android提供jni層,做爲一個適配器。性能
能夠在java層調用c接口,在jni層能夠經過java提供的反射機制調用java接口和建立java對象。測試
最後需求完成了,自測也沒問題,嘻嘻,本身也開心了一下,可是提交測試後,測試人員立刻報了一個bug。google
出現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的錯誤了。