上面這張圖你們都應該很熟了,下面只講下和JNI有關的部分html
記錄正在執行的虛擬機字節碼指令的地址(若是正在執行的是本地方法則爲空)。java
本地方法棧與 Java 虛擬機棧相似,它們之間的區別只不過是本地方法棧爲本地方法服務。 本地方法通常是用其它語言(C、C++ 或彙編語言等)編寫的,而且被編譯爲基於本機硬件和操做系統的程序,對待這些方法須要特別處理。 android
全部對象都在這裏分配內存,是垃圾收集的主要區域("GC 堆")。 堆不須要連續內存,而且能夠動態增長其內存,增長失敗會拋出 OutOfMemoryError 異常。git
能夠經過 -Xms 和 -Xmx 兩個虛擬機參數來指定一個程序的堆內存大小,第一個參數設置初始值,第二個參數設置最大值。github
java -Xmx1024m -Xms1024m
//-Xmx1024m:設置JVM最大可用內存爲1024M。
//-Xms1024m:設置JVM初始內存爲1024m。此值可與-Xmx相同,以免每次垃圾回收完成後JVM從新分配內存。
複製代碼
在Android系統對於每一個應用都有內存使用的限制,機器的內存限制,在/system/build.prop文件中配置的。能夠在manifest文件application節點加入 android:largeHeap="true"
來讓Dalvik/ART虛擬機分配更大的堆內存空間編程
也稱爲C-Heap,供Java Runtime進程使用的,沒有相應的參數來控制其大小,其大小依賴於操做系統進程的最大值。 Java應用程序都是在Java Runtime Environment(JRE)中運行,而Runtime自己就是由Native語言(如:C/C++)編寫程序。Native Memory就是操做系統分配給Runtime進程的可用內存,它與Heap Memory不一樣,Java Heap 是Java應用程序的內存。。Native Memory的主要做用以下:bash
在Java代碼中,Java對象被存放在JVM的Java Heap,由垃圾回收器(Garbage Collector,即GC)自動回收就能夠。多線程
在Native代碼中,內存是從Native Memory中分配的,須要根據Native編程規範去操做內存。如:C/C++使用malloc()/new分配內存,須要手動使用free()/delete回收內存。app
然而,JNI和上面二者又有些區別。 JNI提供了與Java相對應的引用類型(如:jobject、jstring、jclass、jarray、jintArray等),以便Native代碼能夠經過JNI函數訪問到Java對象。引用所指向的Java對象一般就是存放在Java Heap,而Native代碼持有的引用是存放在Native Memory中。jvm
舉個例子,以下代碼:
jstring jstr = env->NewStringUTF("Hello World!");
複製代碼
NewStringUTF()
用於構造一個String對象,該對象存放在Java Heap中,同時返回了一個jstring類型的引用。開發人員都應該遇到過OOM(Out of Memory)異常,在JNI開發中,該異常可能發生在Java Heap中,也可能發生在Native Memory中。
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: native memory exhausted
複製代碼
Java Heap 中出現 Out of Memory異常的緣由有兩種:
1)程序過於龐大,導致過多 Java 對象的同時存在;
2)程序編寫的錯誤致使 Java Heap 內存泄漏。
複製代碼
Native Memory中出現 Out of Memory異常的緣由:
1)程序申請過多資源,系統未能知足,好比說大量線程資源;
2)程序編寫的錯誤致使Native Memory內存泄漏。
複製代碼
JNI引用有三種:Local Reference
、Global Reference
、Weak Global Reference
。下面分別來介紹一下這三種引用內存分配和管理。
Local Reference
只在Native Method執行時存在,只在建立它的線程有效,不能跨線程使用。它的生命期是在Native Method的執行期開始建立(從Java代碼切換到Native代碼環境時,或者在Native Method執行時調用JNI函數時),在Native Method執行完畢切換回Java代碼時,全部Local Reference被刪除(GC會回收其內存),生命期結束(調用DeleteLocalRef()
能夠提早回收內存,結束其生命期)。
實際上,每當線程從Java環境切換到Native代碼環境時,JVM 會分配一塊內存用於建立一個Local Reference Table
,這個Table用來存放本次Native Method 執行中建立的全部Local Reference
。每當在 Native代碼中引用到一個Java對象時,JVM 就會在這個Table中建立一個Local Reference
。好比,咱們調用 NewStringUTF() 在 Java Heap 中建立一個 String 對象後,在 Local Reference Table
中就會相應新增一個 Local Reference
。
Local Reference 表、Local Reference 和 Java 對象的關係
接下來舉個簡單例子說明一下:
jstring jstr = env->NewStringUTF("Hello World!");
複製代碼
Local Reference Table
的內存不大,所能存放的Local Reference
數量也是有限的(在Android中默認最大容量是512個),使用不當就會引發溢出異常Local Reference
並非Native裏面的局部變量,局部變量存放在堆棧中,其引用存放在Local Reference Table
中。在Native Method結束時,JVM會自動釋放Local Reference,但Local Reference Table
是有大小限制的,在開發中應該及時使用DeleteLocalRef()刪除沒必要要的Local Reference,否則可能會出現溢出錯誤:
JNI ERROR (app bug): local reference table overflow (max=512)
複製代碼
在C/C++中實例化的JNI對象,若是不返回java,必須用release掉或delete,不然內存泄露。包括NewStringUTF,NewObject。對於通常的基本數據類型(如:jint,jdouble等),是不必調用該函數刪除掉的。若是返回java沒必要delete,java會本身回收。
Global Reference
Local Reference是在Native Method執行的時候出現的,而Global Reference
是經過JNI函數NewGlobalRef()
和DeleteGlobalRef()
來建立和刪除的。 Global Reference
具備全局性,能夠在多個Native Method調用過程和多線程中使用,在主動調用DeleteGlobalRef以前,它是一直有效的(GC不會回收其內存)。
/**
* 建立obj參數所引用對象的新全局引用。obj參數既能夠是全局引用,也能夠是局部引用。全局引用經過調用DeleteGlobalRef()來顯式撤消。
* @param obj 全局或局部引用。
* @return 返回全局引用。若是系統內存不足則返回 NULL。
*/
jobject NewGlobalRef(jobject obj);
/**
* 刪除globalRef所指向的全局引用
* @param globalRef 全局引用
*/
void DeleteGlobalRef(jobject globalRef);
複製代碼
使用 Global reference
時,當 native code 再也不須要訪問Global reference
時,應當調用 JNI 函數 DeleteGlobalRef()
刪除 Global reference
和它引用的 Java 對象。不然Global Reference
引用的 Java 對象將永遠停留在 Java Heap 中,從而致使 Java Heap 的內存泄漏。
Weak Global Reference
用NewWeakGlobalRef()
和DeleteWeakGlobalRef()
進行建立和刪除,它與Global Reference
的區別在於該類型的引用隨時均可能被GC回收。
對於Weak Global Reference
而言,能夠經過isSameObject()
將其與NULL比較,看看是否已經被回收了。若是返回JNI_TRUE,則表示已經被回收了,須要從新初始化弱全局引用。Weak Global Reference
的回收時機是不肯定的,有可能在前一行代碼判斷它是可用的,後一行代碼就被GC回收掉了。爲了不這事事情發生,JNI官方給出了正確的作法,經過NewLocalRef()獲取Weak Global Reference
,避免被GC回收。
不少人會誤將 JNI 中的 Local Reference 理解爲 Native Code 的局部變量。這是錯誤的。
Native Code 的局部變量和 Local Reference 是徹底不一樣的,區別能夠總結爲:
⑴局部變量存儲在線程堆棧中,而 Local Reference 存儲在 Local Ref 表中。
⑵局部變量在函數退棧後被刪除,而 Local Reference 在調用 DeleteLocalRef() 後纔會從 Local Ref 表中刪除,而且失效,或者在整個 Native Method 執行結束後被刪除。
⑶能夠在代碼中直接訪問局部變量,而 Local Reference 的內容沒法在代碼中直接訪問,必須經過 JNI function 間接訪問。JNI function 實現了對 Local Reference 的間接訪問,JNI function 的內部實現依賴於具體 JVM。
extern "C"
JNIEXPORT jstring JNICALL Java_com_test_application_MainActivity_init(JNIEnv *env, jobject instance, jstring data, jbyteArray array) {
int len = env->GetArrayLength(array);
const char *utfChars = env->GetStringUTFChars(data, 0);
jbyte *arrayElements = env->GetByteArrayElements(array, NULL);
jstring pJstring = env->NewStringUTF(utfChars);
jbyteArray jpicArray = env->NewByteArray(len);
env->SetByteArrayRegion(jpicArray, 0, len, arrayElements);
// TODO
env->DeleteLocalRef(pJstring);
env->DeleteLocalRef(jpicArray);
env->ReleaseStringUTFChars(data, utfChars);
env->ReleaseByteArrayElements(array, arrayElements, 0);
std::string hello = "Hello from C++";
jstring result = env->NewStringUTF(hello.c_str());
return result;
}
複製代碼
其它的還有:
jclass ref= (env)->FindClass("java/lang/String");
env->DeleteLocalRef(ref);
複製代碼
由於根據jni.h
裏的定義:
typedef jobject jclass;
複製代碼
jclass也是jobject。而jmethodID
/jfielID
和jobject沒有繼承關係,它們不是object,只是個整數,不存在被釋放與否的問題。
注意Local Reference的生命週期,若是在Native中須要長時間持有一個Java對象,就不能使用將jobject存儲在Native,不然在下次使用的時候,即便同一個線程調用,也將會沒法使用。下面是錯誤的作法:
jstring global;
extern "C" JNIEXPORT jstring JNICALL
Java_org_hik_libyuv_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
jstring local = env->NewStringUTF(hello.c_str());
global = local;
return local;
}
複製代碼
正確的作法是使用Global Reference,以下:
jstring global;
extern "C" JNIEXPORT jstring JNICALL
Java_org_hik_libyuv_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
jstring local = env->NewStringUTF(hello.c_str());
global = static_cast<jstring>(env->NewGlobalRef(global));
return local;
}
複製代碼
JNIEnv和jobject對象都不能跨線程使用。 對於jobject,解決辦法是
a、m_obj = env->NewGlobalRef(obj);//建立一個全局變量
b、jobject obj = env->AllocObject(m_cls);//在每一個線程中都生成一個對象
複製代碼
對於JNIEnv,解決辦法是在每一個線程中都從新生成一個env
JavaVM *gJavaVM;//聲明全局變量
(*env)->GetJavaVM(env, &gJavaVM);//在JNI方法的中賦值
JNIEnv *env;//在其它線程中獲取當前線程的env
m_jvm->AttachCurrentThread((void **)&env, NULL);
複製代碼
當在一個線程裏面調用AttachCurrentThread後,若是不須要用的時候必定要DetachCurrentThread,不然線程沒法正常退出,致使JNI環境一直被佔用。