Jni解决了哪些问题?
Jni原理
JNI是Java Native Interface(Java本地接口)的缩写。JNI作为java和操作系统间的一个直接接口,可以通过JNI使得java直接调用操作系统的资源。目前JNI只能通过c/C++实现,因为jni只是对操作系统资源调用的一个桥接过程。所以理论上在windows下只要是dll文件均可以被调用。java代码编译之后是运行在一个jvm里,所以java的任何操作对操作系统而言都是隔着一层虚拟机外壳,这点也正式java的优点,帮助java实现了“Write Once, Run Everywhere”的可移植性。但是使用了jni之后必须要明白这个“Write Once, Run Everywhere”要被打破,必须要实现不同的操作系统的各种jni版本。
JNI的开发调用过程可以用下图来完整表示:
如果这个图表示的不够清楚,可以看下面这个图:
内存管理
通过jni创建的对象,都是使用jvm的heap空间。比如jstring、jarray等继承于jobject的类内存的分配实际上都是由jvm管理的,并不是使用c的本地内存。如以下代码片段创建了一个String对象,这个对象可以被以jstring的方式传递给jni框架的调用者,最终在jvm的heap里创建了一个内容为"hello world!"的String对象
char *buf = "hello world!";
(*env)->NewStringUTF(env, buf);
在jni代码里,任何jni基本类型的指针或者对象的创建都是通过直接分配内存的,如果创建的对象是被返回给jni调用者的,那么可以不用管理该对象的内存。如果是临时使用,则必须在使用完成之后释放内存
以下情况需要释放内存:
const jbyte *str;
str = (*env)->GetStringUTFChars(env, prompt, NULL);
...
(*env)->ReleaseStringUTFChars(env, prompt, str);
jstring jstr = env->NewStringUTF((*p).sess_id);
...
env->DeleteLocalRef( jstr);
jobject jobj = env->NewObject(clazz,midInit);
return jobj;
所以一般对这类分配空间的操作都是成对出现:
GetStringUTFChars
ReleaseStringUTFChars
GetStringCritical
ReleaseStringCritical
GetStringRegion
SetStringRegion
Get<Type>ArrayElements
Release<Type>ArrayElements
GetPrimitiveArrayCritical
ReleasePrimitiveArrayCritical
应用场景
总结了一下,jni一般有以下一些应用场景
1.高性能 ,在一些情况下因为处理运算量非常大,为了获取高性能,直接使用java是不能胜任的,如:一些图形的处理
2.调用一些硬件的驱动或者一些软件的驱动,比如调用一些外部系统接口的驱动,如:读卡器的驱动,OCI驱动
3.需要使用大内存,远远超过jvm所能分配的内存,如:进程内Cache
4.调用C或者操作系统提供的服务,如:java调用搜索服务,其中搜索是由C/C++实现的,不过这个一般可以设计成更加通用的方式,比如soa的方式
所有这些场景的前提是牺牲了java代码的可移植性,不同的os,甚至版本都需要写不同版本的native实现
性能
java既然运行在jvm之上,那么jvm又是怎么和操作系统交互的呢?jvm是由c语言写成,对java的接口进行了优化,性能非常的好。
这点也让我以为jni接口调用会性能会非常好,实际情况测试表明jni接口的的调用性能是比较差的
以下是在JDK 1.6.0_07-b06版本windows下的测试结果:
1. 准备了两个空方法,分别是java版本和jni版本的helloNothing,方法里面不包含任何逻辑,测试分为10组,每组循环1亿次
HelloNothing h = new HelloNothing();
int times = 100000000; //亿次
// test native method
for (int n = 0; n < 10; n++) {
long t1 = System.currentTimeMillis();
for (int i = 0; i < times; i++) {
h.helloNothing1();
}
long t2 = System.currentTimeMillis();
System.out.println("round " + n + ": " + (t2 - t1) + "ms");
}
// test java method
for (int n = 0; n < 10; n++) {
long t1 = System.currentTimeMillis();
for (int i = 0; i < times; i++) {
h.helloNothing();
}
long t2 = System.currentTimeMillis();
System.out.println("round " + n + ": " + (t2 - t1) + "ms");
}
java直接调用 helloNothing():
private void helloNothing1(){
// do nothing
}
round 0: 125ms
round 1: 140ms
round 2: 157ms
round 3: 140ms
round 4: 125ms
round 5: 125ms
round 6: 140ms
round 7: 141ms
round 8: 141ms
round 9: 141ms
平均时间:137 ms 每秒执行:7.2亿次
java调用jni的 helloNothing():
private native void helloNothing();
JNIEXPORT void JNICALL Java_com_taobao_pcache_jni_HelloNothing_helloNothing
(JNIEnv *env, jobject obj)
{
// do nothing
}
测试结果:
round 0: 2453ms
round 1: 2156ms
round 2: 2187ms
round 3: 2156ms
round 4: 2157ms
round 5: 2187ms
round 6: 2156ms
round 7: 2156ms
round 8: 2141ms
round 9: 2156ms
平均时间:2190 ms 每秒执行:0.46亿次
java普通空方法和native空方法的调用效率相差在15倍左右,据说在jdk1.6之前的版本性能更差
2. 增加测试方法的复杂度,在方法里创建并返回一个String对象,测试循环1千万次
java被测试的方法:
private String helloString1(){
return "hello world!";
}
round 0: 328ms
round 1: 344ms
round 2: 328ms
round 3: 328ms
round 4: 344ms
round 5: 328ms
round 6: 328ms
round 7: 328ms
round 8: 344ms
round 9: 343ms
平均时间:334 ms 每秒执行:0.3亿次
native被测试的方法:
JNIEXPORT jstring JNICALL Java_com_taobao_pcache_jni_HelloNothing_helloString
(JNIEnv *env , jobject obj)
{
return (*env)->NewStringUTF(env, "hello world!");
}
测试结果:
round 0: 4234ms
round 1: 4484ms
round 2: 4297ms
round 3: 4140ms
round 4: 4094ms
round 5: 4296ms
round 6: 4532ms
round 7: 4391ms
round 8: 4390ms
round 9: 4406ms
平均时间:4326 ms 每秒执行:0.0231亿次
java普通字符串创建方法和native方法的调用效率相差在13倍左右,根据空方法的调用结果可以发现,通过jni new String对象和java直接new String对象消耗的时间上是差不多的,时间最大的消耗在于java调用jni方法的桥接上
。由于环境不一样,所以以上测试结果只是作为一个大致的参考
。
开发过程注意事项
1.windows下编译dll 命令行:
cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 -MD -LD HelloNothing.c -FeHelloNothing.dll
linux下编译成so命令行:
gcc -I/opt/taobao/java/include -I/opt/taobao/java/include/linux -shared -fPIC HelloNothing.c -o libHelloNothing.so
2.dll/so加载问题:
类似下面的错误,表示dll或者so在环境变量路径里不能被找到
java.lang.UnsatisfiedLinkError: no HelloWorld in library path
at java.lang.Runtime.loadLibrary(Runtime.java)
at java.lang.System.loadLibrary(System.java)
at HelloWorld.main(HelloWorld.java)
解决方法:
1.将so或者dll拷贝到path变量指定的路径下
2.在执行的时候指定当前类路径为路径java.library.path的路径:java -Djava.library.path=. HelloWorld
3.如果是tomcat或者jboss则在启动脚本里配置java.library.path变量的路径
3.内存泄露
通过Jni创建的对象全部使用了堆内存,如以下代码片段创建了一个String对象,这个对象可以被以jstring的方式传递给jni框架的调用者,最终在jvm的heap里创建了一个内容为"hello world!"的String对象
char *buf = "hello world!";
(*env)->NewStringUTF(env, buf);
在jni代码里,任何jni对象的创建都是通过直接分配内存的,如果创建的对象是被返回给jni调用者的,那么可以不用管理该对象的内存。如果是临时使用,则必须在使用完成之后释放内存
java基本类型和jni基本类型对应表
Java Language Type Native Type Description
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
对象继承关系
分享到:
相关推荐
解决JNI中文乱码,
解决方案: 请C++同事帮忙写个dll程序,dll去解析开发平台输出的二进制流数据,上层应用平台调用dll得到json报文,然后再去做一些业务处理。 那现在上层应用面临的问题:访问java外部功能接口实现方式(即调用dll)...
JAVAjni,使用JAVA操作INI增删改查文件,UTF-8版,解决中文乱码问题
使用JNI与本地接口,并传递中文字符串。 绝对原创,转载请说明!
jsp,java中文乱码问题另类解决方案,jni加密、解密编码
通过JNI调用本地DLL,并传递中文字符串, Visual C++ 6.0 开发DLL 原创作品,随意转载,提前请说明!
jni获取Android设备MAC Address最完美解决方案。解压import即可。新版本可能不太试用了,请酌情下载
Aptana studio 安装完启动时中出现Failed to load the JNI shared library如何解决?
jni获取Android设备MAC Address最完美解决方案.rar,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
很小的一个JNI调用实例,并且介绍一些JNI调用的一些常见问题的解决办法。
重新整合的jni以及sdk的安装配置,解决不了各种问题,这可是最新的!
在实践开发网格计算服务的过程中遇到了跨平台与服务运行效率的问题,在解决问题的过程中对Java的JNI技术进行了较深入的研究。对开发过程中使用JNI技术的重点和难点所作研究后的一个归纳和总结,并结合具体的开发实例...
解决sqlite_jni no found in library的bug,放在tomcat的bin目录下
解决JAVA对本地操作的一种方法就是JNI。 JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以...
在Android Studio3.0上用javah方式编译jni,并解决新问题
轻松学会JNI层多线程回调java方法,解决在c/c++层时,多线程环境下用过findClass异常问题。
参考百度百科的入门demo,并对本机运行时出现的错误进行描述,相关的解决办法也注明了出外。下载后请先行阅读readme.txt
解决JAVA对本地操作的一种方法就是JNI。 JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以...
最近项目中需要使用JNI,所以研究了一下,其中遇到过不少问题,总结一下,让遇到同样问题的人可以得到解决。 在C/C++中调用Java的方法一般分为五个步骤:初始化虚拟机、获取类、获取类的方法、创建类对象、调用方法...