前言
本篇文章主要从几个方面来阐述下java jni的原理机制。1. 什么是java jni? 2. java jni有什么作用? 3. java jni应用场景有哪些?4. java jni实现原理5. java jni如何使用;6. 使用实例;下面我们逐一进行解析。
什么是java jni
JNI(Java Native Interface)是JAVA标准平台中的一个重要功能,它弥补了JAVA的与平台无关这一重大优点的不足,在JAVA实现跨平台的同时,也能与其它语言(如C、C++)的动态库进行交互,给其它语言发挥优势的机会。有了JAVA标准平台的支持,使JNI模式更加易于实现和使用。
系统环境代指本地操作系统环境,它有自己的本地库和CPU指令集。本地程序(Native Applications)使用C/C++这样的本地语言来编写,被编译成只能在本地系统环境下运行的二进制代码,并和本地库链接在一起。本地程序和本地库一般地会依赖于一个特定的本地系统环境。比如,一个系统下编译出来的C程序不能在另一个系统中运行。
JNI的强大特性使我们在使用JAVA平台的同时,还可以重用原来的本地代码。作为虚拟机实现的一部分,JNI允许JAVA和本地代码间的双向交互。
java jni有什么作用
上一部分已经大概说了java jni的作用,JNI是java平台的一部分,通过java jni可以实现与其他底层语言(例如C、C++)进行交互。JNI是完善JAVA功能的一个重要功能,一方面JVM封装了各种操作系统的差异性,使用JAVA程序可以跨平台运行,另一方面JNI提供了java程序与操作系统相关功能函数交互的接口,不失java功能的全面性。
java jni应用场景
1. 解决性能问题
在程序对时间敏感或对性能要求特别高时,有必要使用更底层的语言,例如使用C或者C++来实现具体功能,然后在java中直接调用这些功能。
2. 解决本机平台接口调用问题
JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。
3. 嵌入式开发应用
“一次编程,到处使用”的Java软件概念原本就是针对网上嵌入式小设备提出的,几经周折,目前SUN公司已推出了J2ME(Java 2 P1atform Micro Edition)针对信息家电的Java版本,其技术日趋成熟,开始投入使用。SUN公司Java虚拟机(JVM)技术的有序开放,使得Java软件真正实现跨平台运行,即Java应用小程序能够在带有JVM的任何硬软件系统上执行。加上Java语言本身所具有的安全性、可靠性和可移植性等特点,对实现瘦身上网的信息家电等网络设备十分有利,同时对嵌入式设备特别是上网设备软件编程技术产生了很大的影响。也正是由于JNI解决了本机平台接口调用问题,于是JNI在嵌入式开发领域也是如火如荼。
java jni实现原理
JNI最重要的设计目标就是在不同操作系统上的JVM之间提供二进制兼容,做到一个本地库不需要重新编译就可以运行不同的系统的JVM上面。
调用过程示意图如下:
从代理模式理解JNI:
java jni如何使用
1. 在java类中申明Native方法(此类为JNI类程序,担任代理角色),并编译成class文件(在此类中同时也需要加载需要使用的类库);
2. 用javah将第一步的class文件生成头文件(具体命令:javah -jni XXX, javah程序统一了java中的native方法、头文件中的函数名和动态库中的函数实现之间的对应关系)
3. 用其他语言(C、C++等)实现上述头文件中的函数,生成动态库,供java程序使用;
4. 发布java和动态库。
使用实例
使用JAVA程序调用C函数进行简单加法计算。这个过程包含下面几个步骤:
-
创建一个类(AddTest.java)声明本地方法。
-
使用javac编译源文件AddTest.java,产生AddTest.class。使用javah –jni来生成C头文件(test_AddTest.h),这个头文件里面包含了本地方法的函数原型。
-
用C代码写函数原型的实现。
-
把C函数实现编译成一个本地库,创建libcommon.dll或者libcommon.so(自定义本地库名称)。
-
使用java命令运行AddTest程序,类文件AddTest.class和本地库libcommon.dll或者libcommon.so)在运行时被加载。
1.创建一个Java类,里面包含着一个native的方法和加载库的方法loadLibrary。AddTest.java代码如下:
package test;
public class AddTest{
static{
System.loadLibrary("common");
}
public native int nativeAdd(int x,int y);
public static void main(String[] args){
AddTest add = new AddTest();
System.out.printf("%d\n",add.nativeAdd(123,123));
}
}
代码很简单,这里声明了nativeAdd(int x,inty)
的方法,执行的时候简单的打出执行的结果。另外这里调用API加载名称叫common
的库,接下来就来实现这个库。
假设当前目录结构如下:
- | - test | AddTest.java
2.生成JNI调用需要的头文件
javac test/AddTest.java
javah -jni test.AddTest
现在的目录结构如下:
- | - test
| AddTest.java
| AddTest.class
| - test_AddTest.h
test_AddTest.h头文件内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class test_AddTest */
#ifndef _Included_test_AddTest
#define _Included_test_AddTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: test_AddTest
* Method: nativeAdd
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_test_AddTest_nativeAdd
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
3.native方法的实现
这里新增common.c
文件来实现之前声明的native方法,目录结构如下:
- | - test
| AddTest.java
| AddTest.class
| - test_AddTest.h
| - common.c
common.c的内容如下:
#include "test_AddTest.h"
JNIEXPORT jint JNICALL Java_test_AddTest_nativeAdd
(JNIEnv * env, jobject obj, jint x, jint y){
return x+y;
}
这里的实现只是简单的把两个参数相加,然后返回。
4.生成动态链接库
gcc -shared -I /usr/lib/jdk1.6/include
-I /usr/lib/jdk1.6/include/linux common.c -o libcommon.so
注意这里几个gcc的选项,-shared是说明要生成动态库,而两个 -I的选项,是因为我们用到<jni.h>相关的头文件,放在<jdk>/include 和 <jdk>/include/linux两个目录下。
最后需要注意一点的是 -o 选项,我们在java代码中调用的是System.loadLibrary("xxx"),那么生成的动态链接库的名称就必须是libxxx.so的形式(这里指Linux环境),否则在执行java代码的时候,就会报 java.lang.UnsatisfiedLinkError: no XXX in java.library.path 的错误!也就是说找不到这个库,我在这里被坑了一小段时间。
好了,现在的目录结构如下:
- | - test
| AddTest.java
| AddTest.class
| - test_AddTest.h
| - common.c
| - libcommon.so
5.执行代码验证结果
java -Djava.library.path=. test.AddTest
246