接續上篇[JNI開發系列④C語言調用構造方法](http://www.jianshu.com/p/b0403771944f)
### 前情提要
在上一篇中 , 我們了解到了 , 創建一個Java對象的幾個步驟:
> 第一,`findClass`找到需要創建對象的類(全類名)
第二,得到構造方法的ID,構造方法名稱,統一使用`<init>`
第三,使用`NewObject`創建Java對象
當創建了這個類的對象之后 , 我們就可以使用這個類里面所提供的方法了 , 那么我們就可以在C中使用Java中其他對象的方法了 。
### 數組引用的處理
在Java中 , 使用`new`關鍵字創建對象 , 創建之后我們就可以隨意使用這個對象 , 我們無需關心這個對象是什么時候被回收的 , 對象的回收已經托管到了JVM的GC , 由GC來幫我們回收無引用的對象 , 那么,我們使用JNI技術傳遞給C/C++的對像要怎么做處理呢 ?
將對象引用傳遞給C/C++時 , C/C++層就會持有Java對象 , 如果不進行妥善處理 , 對象多了就會出現內存泄漏問題 , 所有在C/C++層使用Java對象時 , 需要釋放這個引用 。
下面就來看看數組引用的處理:
```java
// 對數組進行排序
private native void useArraySort(int[] array) ;
public static void main(String[] args) {
// Java數組在C中排序
int[] array = {1,60,20,10,4,90,23} ;
jni.useArraySort(array);
// 輸出
for (int i = 0; i < array.length; i++) {
System.out.println("array == "+array[i]);
}
}
```
將`array`對象傳遞給C , C中的變量將持有`array`這個引用
```c
// sort
int compare(int* a, int* b) {
return (*a) - (*b);
}
/*對數組進行排序*/
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJNI_useArraySort
(JNIEnv *env, jobject jobj , jintArray jarray) {
jint* arrayElemts = (*env)->GetIntArrayElements(env, jarray, NULL);
jsize arraySize = (*env)->GetArrayLength(env, jarray);
qsort(arrayElemts,arraySize,sizeof(jint),compare);
// 釋放引用 , 因為數組和對象在java中都是引用 , 都會在堆內存中開辟一塊空間 , 但我們使用完對象之后
// 需要將引用釋放掉 , 不然會很耗內存 , 在一定程度上可能會造成內存溢出 。
//JNI_ABORT, Java數組不進行更新,但是釋放C/C++數組
//JNI_COMMIT,Java數組進行更新,不釋放C/C++數組(函數執行完,數組還是會釋放)
(*env)->ReleaseIntArrayElements(env, jarray, arrayElemts, JNI_COMMIT);
}
```
內存示意圖:

只要是Java對象 , 在C中都需要釋放,如String類型引用:
```c
// String類型引用釋放
void (JNICALL *ReleaseStringUTFChars)
(JNIEnv *env, jstring str, const char* chars);
```
在C中創建的對象引用也需要進行引用釋放
```c
/*返回int類型的數組*/
JNIEXPORT jintArray JNICALL Java_com_zeno_jni_HelloJNI_getIntArray
(JNIEnv *env, jobject jobj,jint len) {
// 創建一個jint類型的數組
jintArray jArray = (*env)->NewIntArray(env, len);
// 得到數組首個元素指針
jint* arrayElements = (*env)->GetIntArrayElements(env, jArray, NULL);
// 指針運算
int i = 0;
for (; i < len; i++)
{
arrayElements[i] = i;
}
// 同步
(*env)->ReleaseIntArrayElements(env, jArray, arrayElements, JNI_COMMIT);
return jArray;
}
java code
// 在C中生存數組 , 返回到Java中
private native int[] getIntArray(int len) ;
int[] intArray = jni.getIntArray(20);
for (int i = 0; i < intArray.length; i++) {
System.out.println("int array === "+intArray[i]);
}
```
> 為什么在C中創建的對象也需要釋放 ?
在上述代碼中 , 創建一個數組對象 , 并將引用傳遞給了Java層 , 將引用交給了Java之后 , C就需要釋放這個引用 , 不然會一直持有 , GC也不會回收這個對象 。
### 引用的分級
在Java中引用也有強弱之分 , 使用`new`創建的對象就是強引用,也可以使用`WeakReference`將對象包裝成一個弱引用對象 。在C中也不列外 , C中也有一套`全局引用`,`局部引用`,`弱全局引用`等等 。
> 一 , 局部引用
```c
// 局部引用
// 作用:C使用到或自行創建Java對象,需要告知虛擬機在合適的時候回收對象
//局部引用,通過DeleteLocalRef手動釋放對象
//1.訪問一個很大的java對象,使用完之后,還要進行復雜的耗時操作
//2.創建了大量的局部引用,占用了太多的內存,而且這些局部引用跟后面的操作沒有關聯性
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJNI_localRef
(JNIEnv *env, jobject jobj) {
// 找到類
jclass dateClass = (*env)->FindClass(env, "java/util/Date");
// 得到構造方法ID
jmethodID dateConstructorId = (*env)->GetMethodID(env, dateClass, "<init>", "()V");
// 創建Date對象
jobject dateObject = (*env)->NewObject(env, dateClass, dateConstructorId);
// 創建一個局部引用
jobject dateLocalRef = (*env)->NewLocalRef(env, dateObject);
// 省略N行代碼
// 不再使用對象 , 則通知GC回收對象
(*env)->DeleteLocalRef(env, dateLocalRef);
// 因為dateObject也是局部對象,可以直接回收dateObject對象
//(*env)->DeleteLocalRef(env, dateObject);
}
```
> 全局引用
```c
// 全局引用
// 定義全局引用
//共享(可以跨多個線程),手動控制內存使用
jstring globalStr;
/*創建全局引用*/
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJNI_createGlobalRef
(JNIEnv *env, jobject jobj) {
jstring jStr = (*env)->NewStringUTF(env, "I want your love !");
// 創建一個全局引用
globalStr = (*env)->NewGlobalRef(env, jStr);
}
/*使用全局引用*/
JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJNI_useGlobalRef
(JNIEnv *env, jobject jobj) {
return globalStr;
}
/*釋放全局引用*/
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJNI_deleteGlobalRef
(JNIEnv *env, jobject jobj) {
// 釋放全局引用
(*env)->DeleteGlobalRef(env, globalStr);
}
//弱全局引用
//節省內存,在內存不足時可以是釋放所引用的對象
//可以引用一個不常用的對象,如果為NULL,臨時創建
//創建:NewWeakGlobalRef,銷毀:DeleteGlobalWeakRef
```
> 引用緩存
```c
/*變量緩存*/
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJNI_variableCach
(JNIEnv *env, jobject jobj) {
// 找到String類
jclass stringClass = (*env)->FindClass(env, "java/lang/String");
// 得到構造方法ID
jmethodID stringConstructorID = (*env)->GetMethodID(env, stringClass, "<init>", "()V");
// 創建String類
//// 緩存局部變量 , 只創建一次 , 關鍵字static
static jobject stringObject = NULL;
if (stringObject == NULL)
{
stringObject = (*env)->NewObject(env, stringClass, stringConstructorID);
printf("------------- create String object --------------\n");
}
/*jobject stringObject = (*env)->NewObject(env, stringClass, stringConstructorID);
printf("------------- create String object --------------\n");*/
}
```
引用的分級 , 上述代碼都有比較詳細的注釋 ,這里就不多加解釋了 , 說一下全局引用的簡單使用場景。
在開發中 , 我們常常需要初始化一些變量 , 進行全局使用 , 這里我們的全局引用就發揮了作用了 。
```c
// 初始化全局變量
//初始化全局變量,動態庫加載完成之后,立刻緩存起來
jstring initGlobalStr;
JNIEXPORT void JNICALL Java_com_zeno_jni_HelloJNI_initVariable
(JNIEnv *env, jclass jcls) {
jstring initStr = (*env)->NewStringUTF(env, "create global init variable ");
initGlobalStr = (*env)->NewGlobalRef(env, initStr);
}
/*訪問初始化全局變量*/
JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJNI_accessInitGlobalVariable
(JNIEnv *env, jobject jobj) {
return initGlobalStr;
}
__java code__
static{
// 加載動態庫
System.loadLibrary("Hello_JNI") ;
// 初始化全局變量
initVariable();
}
```
C中引用的分級和在Java中的類型 , 都需要在合適的環境使用 。
### 結語
這是JNI系列的最后一篇 , 在這個系列中 , 我們大致了解了JNI的開發流程, 以及一些常用的API和技法 , 在下篇中 , 我們正式進入NDK開發 , 將我們在JNI中學到的應用起來 。NDK基礎過后 , 將會進入到C++語言的學習 , 我們要學會使用第三方C/C++庫 。
> 本系列由[動腦學院](http://www.dongnaoedu.com/)提供技術支持 , 動腦學院 -- 做一家受人尊重的企業,做一個令人尊敬的老師 !
### 參考
[global_and_local_references](http://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#global_and_local_references)
- 簡介
- C語言基礎及指針①語法基礎
- C語言基礎及指針②之指針內存分析
- C語言基礎及指針③函數與二級指針
- C語言基礎及指針④函數指針
- C語言基礎及指針⑤動態內存分配
- C語言基礎及指針⑥字符操作
- C語言基礎及指針⑦結構體與指針
- C語言基礎及指針⑧文件IO
- C語言基礎及指針⑨聯合體與枚舉
- C語言基礎及指針⑩預編譯及jni.h分析
- JNI開發系列①JNI概念及開發流程
- JNI開發系列②.h頭文件分析
- JNI開發系列③C語言調用Java字段與方法
- JNI開發系列④C語言調用構造方法
- JNI開發系列⑤對象引用的處理
- NDK開發基礎①使用Android Studio編寫NDK
- NDK開發基礎②文件加密解密與分割合并
- NDK開發基礎③增量更新之服務器端生成差分包
- C++基礎①命名空間結構體和引用