JNI技术---clojure 调用C++库的方法
Posted willwillie
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JNI技术---clojure 调用C++库的方法相关的知识,希望对你有一定的参考价值。
本文目的是加深理解—to JNI and clojure。本文可能比较抽象,时隔多月,自己再次来读的时候都觉得这文章晦涩难懂,如果你有疑问欢迎提问。
JNI概述
JNI,是Java Native Interface的缩写,中文为Java本地调用。
JavaTM Native Interface (JNI) is a standard programming interface for writing Java native methods and embedding the JavaTM virtual machine* into native applications. The primary goal is binary compatibility of native method libraries across all Java virtual machine implementations on a given platform.
要了解这个概念,事先需要了解的有:
1.什么是Java native 方法?
Java程序中的函数可以调用Native语言写的函数,Native一般指的是C/C++编写的函数。
(也就是将C/C++代码移植到JVM上面。。。)
当然反过来, Native程序中的函数可以调用Java层的函数,也就是在C/C++程序中可以调用Java的函数。(不过这一点还不太care)
JVM和本地方法
Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。通过对中央处理器(CPU)所执行的软件实作,实现能执行编译过的Java程序码(Applet与应用程序)。
作为一种编程语言的虚拟机,实际上不只是专用于Java语言,只要生成的编译文件匹配JVM对载入编译文件格式要求,任何语言都可以由JVM编译运行。—wiki
承载Java世界的虚拟机是用Native语言写的,而虚拟机又运行在具体平台上,所以虚拟机本身无法做到平台无关。然而,有了JNI技术,就可以对Java层屏蔽具体的虚拟机实现上的差异了。这样,就能实现Java本身的平台无关特性。其实Java一直在使用JNI技术,只是我们平时较少用到罢了。
(因为要理解classpath(classLoader去读取的路径) java.library.path,目前来说最好的办法就是去理解JVM。)
JVM的架构基本如下所示:
这里的主线是classLoader,运行时数据区域,和执行引擎。再之后就可以较好的理解native method interface和native method library了。
classLoader:作用:装载.class文件 classloader 有两种装载class的方式 (时机):
- 隐式:运行过程中,碰到new方式生成对象时,隐式调用classLoader到JVM
- 显式:通过class.forname()动态加载
类加载器 classloader 是具有层次结构的,也就是父子关系。其中,Bootstrap 是所有类加载器的父亲。当运行 java 虚拟机时,这个类加载器被创建,它负责加载虚拟机的核心类库,如 java.lang.* 等
执行引擎:
作用: 执行字节码,或者执行本地方法(也就是说本地方法不会被编译为字节码)
JVM 运行时数据区 (JVM Runtime Area) 其实就是指 JVM 在运行期间,其对JVM内存空间的划分和分配。JVM在运行时将数据划分为了6个区域来存储。
程序员写的所有程序都被加载到运行时数据区域中,不同类别存放在heap, java stack, native method stack, PC register, method area.
PC程序计数器:如该方法为native的,则PC寄存器中不存储任何信息。Java 的多线程机制离不开程序计数器,每个线程都有一个自己的PC,以便完成不同线程上下文环境的切换。
heap:存放程序公共的new过程的对象等;
java stack是虚拟的java方法调用栈;
native method stack:本地方法调用栈;
method area:存放对象的filed,方法等内容。
从这里可以看出,本地方法的对象会在JVM的heap中分配对象,在JVM的native method stack保存调用栈;并被JVM执行引擎执行。
然而JVM的GC机制没法释放本地方法的对象,故而需要手动释放本地方法生成的对象。
目标是?
JNI的目标很简单,一方面避免重复造轮子,另一方面可以利用底层语言的效率和速度。
早在Java语言诞生前,很多程序都是用Native语言写的,它们遍布在软件世界的各个角落。Java出世后,它受到了追捧,并迅速得到发展,但仍无法对软件世界彻底改朝换代,于是才有了折中的办法。既然已经有Native模块实现了相关功能,那么在Java中通过JNI技术直接使用它们就行了,免得落下重复制造轮子的坏名声。另外,在一些要求效率和速度的场合还是需要Native语言参与的。—http://wiki.jikexueyuan.com/project/deep-android-v1/jni.html
所以现在的整体架构是:
使用Java调用c++的库
现在可以展现一个具体的例子,看看Java是怎么调用native方法的。
例:GLPK是一个线性规划的引擎,使用单纯形法和分支定界法等来解决线性规划问题。
GLPK被实现为一个C++库,通过安装这个so动态库到默认目录/usr/local/lib
用户就可以轻易地调用glpk的代码来解决线性规划问题。
那么怎么使用Java来调用这个glpk引擎呢?开源社区已经有了支持,GLPK-JAVA,project GLPK for Java delivers a Java Binding for GLPK.
GLPK for Java glpk-java is designed for
GLPK 4.60 and
OpenJDK 1.6 or higher
在源码安装glpk到/usr/local/lib
后,同时安装libglpk-java.jar文件到默认目录/usr/local/lib
,也安装了glpk jni到默认目录/usr/local/lib/jni
,现在的拓扑结构如下(忽略版本信息):
注意,libglpk-java的安装需要有SWIG的支持。SWIG一个用于从C/C++产生高级语言的glue技术,可以人为SWIG利用原本的libglpk.so生成相应的JNI代码,供java版本的GLPK native函数调用。
到此,环境就搭建好了。接下来就可以进行测试了,
编译javac:
javac -cp /usr/local/share/java/glpk-java.jar Test.java
编译的时候需要指定classpath,将glpk-java .jar作为依赖的jar文件。
运行:
java -Djava.library.path =/usr/local/lib/jni \\
-cp /usr/local/share/java/glpk -java.jar :.\\Test
运行时除了需要指定classpath,还需要指定运行时的动态so库的路径:java.library.path
。
java.library.path
用于指定要使用的本地方法的so或者dll库。实现JNI接口的C++ so文件是可以被JVM调用的,如第一张图所示。
java.library.path
可以通过程序获得:
public class PrintPath
public static void main(String[] args)
System.out.println(System.getProperty("java.library.path"));
我的环境现在的path是:
# java PrintPath
/usr/local/lib/jni/:/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
对于使用了maven构建的project,是可以通过pom.xml来指定依赖的,而不需要默认目录中的glpk-java.jar。
在clojure中调用Java的JNI
前面论述了怎么在java中调用本地方法,那么怎么在clojure中调用本地方法呢?一样的,因为clojure中可以调用Java类和方法。
但是因为clojure使用了lein或者boot的构建方式,所以使用java -D来指定运行时的library并不是那么方便。。。
如果/usr/local/lib/jni(libglpk-jsva.so的默认安装路径)不在java.library.path
范围内,则可以将/usr/local/lib/jni有关glpk的so文件拷贝到java.library.path的默认路径下,比如/usr/lib目录下,也可以通过System.setProperty("java.library.path",xxx)
动态设置so文件的路径。另一方面lein或者boot都可以指定依赖,最终jar包会被下载到本地库~/.m2目录下,故而不再需要别的配置和工作。
so,在clojure环境中就可以灵活的调用native方法了(完结)。
另外一个碰到的问题:
jni并不是在所有的情况下都可以调用成功的,在某些环境(比如低版本的内核和gcc版本)下,会导致JVM crash,可以分析这个过程生成的log文件来分析JVM crash的原因。
也有一个更好的办法,就是拷贝正常能运行环境下的jni.so文件到异常的环境(机器的位数相同)下同样的目录。因为so文件是二进制文件,可以直接由机器执行,也不需要jvm的解释器,直接由机器运行。
以上是关于JNI技术---clojure 调用C++库的方法的主要内容,如果未能解决你的问题,请参考以下文章
JNI 保持对对象的全局引用,并使用其他 JNI 方法访问它。在多个 JNI 调用中保持 C++ 对象处于活动状态