`
jjw
  • 浏览: 64705 次
文章分类
社区版块
存档分类
最新评论

Jni解决了哪些问题?

阅读更多

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

对象继承关系

 


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics