- 说明:只是学习总结,没有深入分析,没有涉及JVM,内存归属等
- 原理:一套Java与本地C/C++/汇编交互规范,由JNIEnv实现。
-
优势:
- 利用 native code 的平台相关性,在平台相关的编程中彰显优势。
- 对 native code 的代码重用。
- native code 底层操作,更加高效。
- JNI类型映射表:
表A
Java 类型 本地类型 描述 boolean jboolean C/C++8位整型 byte jbyte C/C++带符号的8位整型 char jchar C/C++无符号的16位整型 short jshort C/C++带符号的16位整型 int jint C/C++带符号的32位整型 long jlong C/C++带符号的64位整型e float jfloat C/C++32位浮点型 double jdouble C/C++64位浮点型 Object jobject 任何Java对象,或者没有对应java类型的对象 Class jclass Class对象 String jstring 字符串对象 Object[] jobjectArray 任何对象的数组 boolean[] jbooleanArray 布尔型数组 byte[] jbyteArray 比特型数组 char[] jcharArray 字符型数组 short[] jshortArray 短整型数组 int[] jintArray 整型数组 long[] jlongArray 长整型数组 float[] jfloatArray 浮点型数组 double[] jdoubleArray 双浮点型数组 ※ JNI类型映射
- 用GetXXXArrayElements和ReleaseXXXArrayElements实现对基本类型数组操作,函数表:
表B
函数 Java 数组类型 本地类型 GetBooleanArrayElements jbooleanArray jboolean GetByteArrayElements jbyteArray jbyte GetCharArrayElements jcharArray jchar GetShortArrayElements jshortArray jshort GetIntArrayElements jintArray jint GetLongArrayElements jlongArray jlong GetFloatArrayElements jfloatArray jfloat GetDoubleArrayElements jdoubleArray jdouble JNI数组存取函数
- 用GetObjectArrayElement函数和SetObjectArrayElement实现对象数组操作。
- 通个GetObjectClass或FindClass得到一个类。如jclass objClass = (env)->FindClass("java/lang/Object");
- 通过以下一些规则和方法操作一个对象:
-
表C
函数 描述 GetFieldID 得到一个实例的域的ID GetStaticFieldID 得到一个静态的域的ID GetMethodID 得到一个实例的方法的ID GetStaticMethodID 得到一个静态方法的ID ※域和方法的函数
表D
Java 类型 符号 boolean Z byte B char C short S int I long L float F double D void V objects对象 Lfully-qualified-class-name;L类名 Arrays数组 [array-type [数组类型 methods方法 (argument-types)return-type(参数类型)返回类型 ※确定域和方法的符号
- JNI使用N大注意点:
A 一定要缓存方法 ID、字段 ID和类
B 数组操作避免触发数组副本:GetTypeArrayRegion() 和 SetTypeArrayRegion() 方法允许您获取和更新数组的一部分,而不是整个数组。比较好的方法是在一个调用中获取大小合理的数组部分,然后再迭代所有这些元素,重复操作直到覆盖整个数组。
C 传一个有多个字段的对象然后回访,比单独传递各个字段要慢很多。
D 不要错误认定本机代码与 Java 代码之间的界限:在设计 Java 代码与本机代码之间的界限时应该最大限度地减少两者之间的相互调用。
E 使用大量本地引用,而未通知 JVM:要及时DeleteLocalRef。
F 使用错误的 JNIEnv:仅在相关的单一线程中使用
JNIEnv
。
G 未检测异常:主动检查异常,if((*env)->ExceptionOccurred(env)) return;
H 未检测返回值:调用任何一个方法后,先判断返回值,再继续往下。
I 未正确使用数组方法: 记得调用
ReleaseXXX()
,否则对数组的更改不会被复制回去。然后确保代码不会在GetXXXCritical()
和ReleaseXXXCritical()
调用之间发起任何 JNI 调用或由于任何原因出现阻塞。J 未正确使用全局引用:始终跟踪全局引用,并确保不再需要对象时删除它们。
K 解决方法:
根据规范验证新代码
分析方法跟踪
使用 -verbose:jni 选项
生成转储
执行代码审查L 如何避免GC:临界区的使用Critical region,Global Reference
M 用JNI_OnLoad和JNI_UnLoad实现JNI版本管理与动态注册JNI方法(*env)->RegisterNatives
jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jclass clazz; //获取JNI环境对象 if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { LOGE("ERROR: GetEnv failed\n"); return JNI_ERR; } //注册本地方法.Load 目标类 clazz = (*env)->FindClass(env,classPathName); if (clazz == NULL) { LOGE("Native registration unable to find class '%s'", classPathName); return JNI_ERR; } //注册本地native方法 if((*env)->RegisterNatives(env, clazz, methods, NELEM(methods)) < 0) { LOGE("ERROR: MediaPlayer native registration failed\n"); return JNI_ERR; } /* success -- return valid version number */ return JNI_VERSION_1_4; }
- 几点弊端:
A 从 Java 环境到 native code 的上下文切换耗时、低效。
B JNI 编程,如果操作不当,可能引起 Java 虚拟机的崩溃。
C JNI 编程,如果操作不当,可能引起内存泄漏。
- 避免Native memory内存泄露(JVM内存包括Java Heap与Nativememory):
A Native Code 本身的内存泄漏: 一定要遵循每门native 的编程语言自身的内存管理机制。
B Global Reference 引入的内存泄漏:一定记得释放
C JNI 编程中潜在的内存泄漏— LocalReference :LocalReference Table对LocalReference 总数有限制,同时切记一定LocalReference 的自动释放一定是JNI返回java层。
- 参考文档:
A 官方技术文档:SUN JNI Tutorial, http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/jniTOC.htmlB JNI中C++和Java的参数传递:http://www.blogjava.net/china-qd/archive/2006/04/29/44002.html
C 官方中文版:http://blog.csdn.net/a345017062/article/details/8068915 http://blog.csdn.net/a345017062/article/details/8068909
E 如何避免内存泄露:http://www.ibm.com/developerworks/cn/java/j-lo-jnileak/
F 使用 Java Native Interface 的最佳实践 http://www.ibm.com/developerworks/cn/java/j-jni/ http://blog.csdn.net/tomken_zhang/article/details/6890333