- 基本类型的 java 与 c 对照图
Java 类型 | 本地 C 类型 | 实际表示的 C 类型 (Win32) | 说明 |
boolean | jboolean | unsigned char | 无符号,8 位 |
byte | jbyte | signed char | 有符号,8 位 |
char | jchar | unsigned short | 无符号,16 位 |
short | jshort | short | 有符号,16 位 |
int | jint | long | 有符号,32 位 |
long | jlong | __int64 | 有符号,64 位 |
float | jfloat | float | 32 位 |
double | jdouble | double | 64 位 |
void | void | N/A | N/A |
在jni.h 和 jni_md.h中声明
typedef unsigned char jboolean;
typedef unsigned short jchar;
typedef short jshort;
typedef float jfloat;
typedef double jdouble;
typedef long jint;
typedef __int64 jlong;
typedef signed char jbyte;
- Java对象类型
Java对象在C\C++代码中的形式如下:
class _jclass : public _jobject {};
class _jthrowable : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jobjectArray : public _jarray {};
- Java数组在本地代码中的处理
我们可以使用GetFieldID获取一个Java数组变量的ID,然后用GetObjectFiled取得该数组变量到本地方法,返回值为jobject,然后我们可以强制转换为j<Type>Array类型。
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;
j<Type>Array类型是JNI定义的一个对象类型,它并不是C/C++的数组,如int[]数组,double[]数组等等。所以我们要把j<Type>Array类型转换为C/C++中的数组来操作。
JNIEnv定义了一系列的方法来把一个j<Type>Array类型转换为C/C++数组或把C/C++数组转换为j<Type>Array。
// 获取&释放primitive数组
void * GetPrimitiveArrayCritical(jarray array, jboolean*isCopy)
void ReleasePrimitiveArrayCritical(jarray array, void*carray, jint mode)
上面是JNIEnv提供给本地代码调用的数组操作函数,大致可以分为下面几类:
1) 获取数组的长度
jsize GetArrayLength(jarray array);
2) 对象类型数组的操作
jsize GetArrayLength(jarray array) // 获得数组的长度
jobjectArray NewObjectArray(jsize len, jclass clazz, jobjectinit) // 创建对象数组,指定其大小
jobject GetObjectArrayElement(jobjectArray array, jsizeindex) // 获得数组的指定元素
void SetObjectArrayElement(jobjectArray array, jsizeindex,jobject val) // 设置数组元素
JNI没有提供直接把Java的对象类型数组(Object[ ])直接转到C++中的jobject[ ]数组的函数。而是直接通过Get/SetObjectArrayElement这样的函数来对Java的Object[ ]数组进行操作
3) 对基本数据类型数组的操作
基本数据类型数组的操作方法比较多,大致可以分为如下几类:
// 创建数组
jbooleanArray NewBooleanArray(jsize len) // 创建Boolean数组,指定其大小
// 获得指定类型数组的元素
jboolean * GetBooleanArrayElements(jbooleanArray array,jboolean *isCopy)
// 释放指定数组
void ReleaseBooleanArrayElements(jbooleanArrayarray,jboolean *elems,jint mode)
// 获取某区域的数据
void GetBooleanArrayRegion(jbooleanArray array,jsizestart, jsize len, jboolean *buf)
// 设置某区域的数据
void SetBooleanArrayRegion(jbooleanArray array, jsizestart, jsize len,const jboolean *buf)
这类函数可以把Java基本类型的数组转换到C/C++中的数组。有两种处理方式,一是拷贝一份传回本地代码,另一种是把指向Java数组的指针直接传回到本地代码,处理完本地化的数组后,通过Realease<Type>ArrayElements来释放数组。处理方式由Get方法的第二个参数isCopied来决定。
Realease<Type>ArrayElements(<Type>Arrayarr,<Type>* array, jint mode)用这个函数可以选择将如何处理Java和C/C++本地数组:
其第三个参数mode可以取下面的值:
l 0:对Java的数组进行更新并释放C/C++的数组
l JNI_COMMIT:对Java的数组进行更新但是不释放C/C++的数组
l JNI_ABORT:对Java的数组不进行更新,释放C/C++的数组
- 局部引用与全局引用
1) JNI中的引用变量
Java代码与本地代码里在进行参数传递与返回值复制的时候,要注意数据类型的匹配。对于int, char等基本类型直接进行拷贝即可,对于Java中的对象类型,通过传递引用实现。VM保证所有的Java对象正确的传递给了本地代码,并且维持这些引用,因此这些对象不会被Java的gc(垃圾收集器)回收。因此,本地代码必须有一种方式来通知VM本地代码不再使用这些Java对象,让gc来回收这些对象。
JNI将传递给本地代码的对象分为两种:局部引用和全局引用。
l 局部引用:只在上层Java调用本地代码的函数内有效,当本地方法返回时,局部引用自动回收。
l 全局引用:只有显示通知VM时,全局引用才会被回收,否则一直有效,Java的gc不会释放该引用的对象。
默认的话,传递给本地代码的引用是局部引用。所有的JNI函数的返回值都是局部引用。
2) 手动释放局部引用情况
虽然局部引用会在本地代码执行之后自动释放,但是有下列情况时,要手动释放:
l 本地代码访问一个很大的Java对象时,在使用完该对象后,本地代码要去执行比较复杂耗时的运算时,由于本地代码还没有返回,Java收集器无法释放该本地引用的对象,这时,应该手动释放掉该引用对象。
(*env)->DeleteLocalRef(env, elemArr);
这个情形的实质,就是允许程序在native方法执行期间,java的垃圾回收机制有机会回收native代码不在访问的对象。
l 创建的工具函数,它会被未知的代码调用,在工具函数里使用完的引用要及时释放。
l 不返回的本地函数。例如,一个可能进入无限事件分发的循环中的方法。此时在循环中释放局部引用,是至关重要的,这样才能不会无限期地累积,进而导致内存泄露。
局部引用只在创建它们的线程里有效,本地代码不能将局部引用在多线程间传递。一个线程想要调用另一个线程创建的局部引用是不被允许的。将一个局部引用保存到全局变量中,然后在其它线程中使用它,这是一种错误的编程。
3) 全局引用
在一个本地方法被多次调用时,可以使用一个全局引用跨越它们。一个全局引用可以跨越多个线程,并且在被程序员手动释放之前,一直有效。和局部引用一样,全局引用保证了所引用的对象不会被垃圾回收。
JNI允许程序员通过局部引用来创建全局引用, 全局引用只能由NewGlobalRef函数创建。下面是一个使用全局引用例子:
stringClass = (*env)->NewGlobalRef(env, localRefCls);
(*env)->DeleteLocalRef(env, localRefCls);
4) 释放全局引用
在native代码不再需要访问一个全局引用的时候,应该调用DeleteGlobalRef来释放它。如果调用这个函数失败,Java VM将不会回收对应的对象。