高性能JAVA开发之JNI程序设计与性能优化

http://hi.baidu.com/ryouaki/item/1bc6bbbb448f87e84fc7fd13

 

 

================================================================================

 

 

前言:

      JAVA本地接口(JNI),是JAVA比较特殊的课题,因为JAVA本地接口(JNI)设计不只是JAVA语言设计,它还是JAVA与C或C++程序设计语言结合的课题.

      通常来说一般会在如下情况使用JNI技术:

      1,应用需要调用JAVA语言不支持的依赖于系统平台的特性.

      2,为了整合一些遗留下来的非JAVA语言开发的系统.

      3,为了创建节省时间的应用,不得不采用低级语言.然后通过JAVA调用.

简单的例子:

JAVA:

class Employee {

      public native void showSalary();

      static {

            System.loadLibrary("Employee"); // c/c++语言的库文件,windows用dll,linux/unix用so

      }

      public static void main(String[] args) {

            (new Employee()).showSalary();

      }

}

C

头文件:*.h

      JNI对于本地方法的声明格式有特殊要求,但是JAVA提供了一个非常好用的工具,操作如下:

      >javac Employee.java // 得到class文件

      >javah Employee      // 为Employee.class文件生成对应的Employee.h

生成文件大致如下(省略一些注释和没用的代码,熟悉c语言的人应该很容易看懂):

#include <jni.h> //该文件存在于jdk厂商提供的jdk安装包内,通常位于安装目录include目录下

#ifndef _Included_Employee

#define _Included_Employee

#ifdef   __cplusplus

extern "C" {

#endif

JNIEXPORT void JNICALL Java_Employee_showSalary(JNIEnv*,jobject);

#ifdef    __cplusplus

}

#endif

#endif

创建*.c文件:

#include <jni.h>

#include "Employee.h"

#include <stdio.h>

JNIEXPORT void JNICALL Java_Employee_showSalary(JNIEnv* env, jobject obj){

      printf("HELLO WORLD");

      return;

}

编译成库:

windows :cl -Ic:\java\include -Ic:\java\include\win32 -LD Employee.c -F employee

linux       :gcc -shared -i$JDK_HOME/include -I/usr/include Employee.c -o libemployee.co

UNIX/Solaris:cc -G -I/usr/local/java/include -I/usr/local/java/include/solaris Employee.c -o libemployee.so

执行:

将库文件放入系统目录,WINDOWS为WINNT/System32,linux/unix为LD_LIBRARY_PATH,也可以设置系统路径

setenv LD_LIBRARY_PATH /*.so路径/:$LD_LIBRARY_PATH

>java -cp . Employee

JNI技术中数据类型与处理方法

      JNI定义了本地数据类型与JAVA的数据类型的对照表:

      boolean                                       jboolean                                           8,unsigned

       byte                                              jbyte                                                 8

       char                                              jchar                                                 16,unsigned

       short                                            jshort                                               16

       int                                                jint                                                     32

       long                                             jlong                                                 64

       float                                             jfloat                                                 32

       double                                        jdouble                                              64

       void                                              void                                                  n/a

在jni.h文件中定义有对访问对象实例函数与用来访问对象实例属性的方法.GetXXXField/GetStaticXXXField用来获取相应变量域的值和静态域的值,还有对应的用来设置值的函数SetXXXField/SetStaticXXXField函数.除了对域访问的函数以外还有对应的访问方法的函数一般格式如下CallXXXMethod/CallStaticXXXMethod,其中XXX代表访问类型.由于函数众多不一一介绍,具体定义都在jni.h文件里

访问JNI本地数据类型的方法

      如下:

      JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv* env, jobject obj, jstring prompt) {

            char buff[128];

            const char *str = (*env)->GetStringUTFChars(env, prompt,0); //从java获取数据

            printf("%s",str);

            (*env)->ReleaseStringUTFChars(env,prompt,str); //释放资源

           scanf("%s\n",buff);

            return (*env)->NewStringUTF(env,buff); // 返回jstring类型

       }

使用JNIEnv接口指针:

      本地方法中使用JAVA对象,例如String,都要通过本地环境接口指针来完成,如上例.而且他本身也是调用函数的第一个参数.

在JNI本地方法中访问数组:

      JNIEXPORT jint JNICALL Java_IntArray_releaseArray(JNIEnv *env, jobject obj, jintArray arr){

      ...

      int i;

       int sum = 0;

      jsize len = (*env)->GetArrayLength(env,arr);

      jint *body = (*env)->GetFloatArrayElements(env,arr,0);

      for (i=0;i<len;i++){ sum+= body[i];}

      (*env)->ReleaseIntArrayElements(env,arr,body,0);

      return sum;

      }

当然也有对应于不同基本类型数组的模版函数Get<type>ArrayElements/Release<type>ArrayElements,但是如果是一个非常巨大的数组的话,也要一次取全部数组?这样会有很大的内存消耗的.JNI因此也提供了一个可以指定读取某一处元素的函数Get/Set<type>ArratRegin();

当元素为一个对象的时候可以使用GetObjectArrayElement()/SetObjectArrayElement()来取和更新指定元素.

JNI中的主要技术:

局部引用与全局引用

由于JNI一般情况下将为JAVA对象创建一个本地引用,这是因为本地引用可以保证被JAVA虚拟机最终在系统中释放,归还系统资源,当程序在那个创建局部引用的本地方法中执行了返回操作后,这个局部引用也就无效了.因此应该避免如下写法:

static jclass cls = 0;

JNIEXPORT void JNICALL Java_FieldAccess_accessFields(JNIEnv* env, jobject obj) {

      cls = (*env)->GetObjectClass(env,obj);

      ......

}

当第二次调用该函数的时候,由于cls指向的引用已经失效,所以会引起错误.正确的方法是:

static jclass cls = 0;

JNIEXPORT void JNICALL Java_FieldAccess_accessFields(JNIEnv* env, jobject obj) {

      jclass clsTmp = (*env)->GetObjectClass(env,obj);

      clsTmp= (*env)->NewGlobalRef(env,cls1);

      ......

}

这样这个引用对象会一直存在,当不在使用的时候会自动被JVM回收或直到调用函数DeleteGlobalRef(),所以要记得用完后释放该全局引用对象.但是当创建大量局部引用,或者大数据量对象的时候,为了避免局部引用表溢出,需要显式调用DeleteGlobalRef(),

处理本地方法引起的JAVA错误:

...

(*env)->ExceptionDescribe(env); // 输出一些调试信息

(*env)->ExceptionClear(env);      // 清除以前的异常信息

jthrowable newExceCls = (*env)->FindClass(env,"java/lang/IllegalArgumentException");

if(newExceCls==0) return ;

(*env)->ThrowNew(env,newExecCls,"thrown from c code"); //如果存在异常,抛出交由JAVA端处理.

...

当然也可以使用ExceptionOccurred()

...

jthrowable cls = (*env)->ExceptionOccurred(env);

if(cls != 0) (*env)->ThrowNew(env,cls ,"thrown from c code");

线程与本地方法

      1,JNI接口指针JNIEnv*仅在当前线程有效,不能传递或者保留到全局引用中以备后用.

      2,绝对不可以将一个局部引用从一个线程传递给另一个线程.

      3,合理仔细使用全局变量.

本地方法的线程同步

...

(*env)->MonitorEnter(env,obj);

...//同步代码

(*env)->MonitorExit(env,obj);

...

参考:JAVA优化编程

posted @ 2013-12-06 14:29  莫装逼、白了少年头  阅读(872)  评论(0)    收藏  举报