什么是JNI?
Java Native Interface 即Java本地接口,它允许Java 代码和其他语言写的代码进行交互。提供了一些API 来与其他语言进行通信(主要是C/C++)。
为什么要使用JNI ?
在C/C++ 中写的程序,可以避开JVM 的内存开销过大的限制,处理高性能的计算,直接调用系统服务(如驱动)等功能。
就是这种情况下,用Java 代码没有C/C++效率高,使用JNI
JVM:jvm是java虚拟机在jni 层的代表,全局只有一个
JNIENV:代表java在本线程的运行环境,每个线程都有一个
JOBJECT:在JNI中除了基本类型数组、Class、String和Throwable外其余所有Java 对象的数据类型在JNI 中都用 jobject表示
JNI的工作原理?
1. java层调用system.load方法。
2. 通过classloader拿到了so的绝对路径,然后调用nativeload()方法。
3. 通过linux下的dlopen方法,加载并查找so库里的方法,如有jni_onload会优先加载。
4. 当前线程下的jnienv会将所有的jni方法注册到了同一个vm中,so和class到了同一个进程空间。
5. 通过当前线程的jnienv即可调用对应的对象方法了。
为什么要在JNI层缓存ID?
先看一张native 方法和java 方法的调用同样方法的耗时统计图,可以看到两者的耗时差距为11倍
原因:
C++调用java需要查找类,查找方法,查找方法ID,获取字段或者方法的调用有时候会需要在JVM中完成大量工作,因为字段和方法可能是从超类中继承而来的,为特定类返回的id不会在Jvm进程生存期间发生变化 ,这会让jvm向上遍历类层次结构来找到它们,这是个开销很大的操作。
所以,缓存ID字段是为了降低CPU负载,提高运行速度,节约电量。
JNI里的缓存类型:
Global Reference:
全局引用生存周期为创建后,直到程序员显示的释放它,否则一直存在。
全局引用可以在多线程之间共享其指向的对象。
Local Reference :
局部引用生存周期为创建后,直到DeleteLocalRef . 或在该方法结束后没有被JVM发现有JAVA层引用而被JVM回收并释放。
局部引用只对当前线程有效,多个线程间不能共享局部引用。
注意:
基于谁创建谁销毁的原则,native函数执行完后,局部引用没有被native代码显示删除,那么局部引用在JVM中还是有效的,JVM决定什么时候删除它,和C语言的局部变量含义是不一样的。
局部引用在JVM中是有个数限制的,默认16个,注意管理释放。
Weak Global Reference :
弱全局引用生命周期为创建之后,直到DeleteGlobalRef。或在内存紧张时进行回收而被释放。
注意:
使用弱全局变量的时候,要时刻记着:它所指向的对象可能已经被垃圾回收了。可通过静态变量和全局变量来保持弱全局引用。
缓存方法推荐:jobject默认是local Ref,函数环境消失时会跟随消失
在jni_onload初始化全局引用和弱全局引用
jmethodID/jfielID和jobject没有继承关系,他不是个object,只是个整数,不存在被释放与否的问题,可用全局变量保存。
jclass是由jobject继承而来的类,所以它是个jobject,需要用弱全局引用来缓存jclass对象。
局部引用管理new出来的对象,注意及时delete。
总体原则,注意释放所有对jobject的引用。
缓存与不缓存的效果对比:调用速度提高40倍
TIPS:
1. 不同线程使用JNIEnv*对象,需要AttachCurrentThread将env挂到当前线程,否则无法使用env。
2. 尽量避免频繁调用JNI或者是使用JNI传输大量到数据。
转自:JNI内存管理