《Java平台体系》——第二章 JVM——JNI(Java本地接口)

当我们了解JVM大量的优点之后,不仅学习Java语言的朋友很激动,非Java语言的朋友可能也有些激动。例如熟悉C的朋友一定喜欢上Java的“高级”语言特性,所以考虑把一些繁琐的对性能要求不是很高的程序功能用Java开发,而把一些对运算性能要求高的用C开发(注意:这句话是我为了吸引大家写的,对于性能往往存在想当然的陷阱,请不要把JNI理解成来解决Java性能瓶颈的途径,即使有人这么做,我们也要建立在测试数据的基础上)。那么大家一定会遇到如何在C中调用JVM和Java程序,如何在Java中调用C程序。那么让我们从JNI开始吧。

JNI也从规范层面有所定义,可以参考http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html​。

在学习之前我们还是发挥一下我们自己的想象。

JNI解决的问题就是程序和程序之间的互调。我们可以想象的方法有:
□ 进程之间的调用。进程之间的调用可以通过管道技术和网络技术来实现(Java也可以做到进程之间的调用)。
□ 进程内调用。让不同来源的程序都在本地代码范围内在一个进程中进行调用。

我们谈的JNI属于进程内调用。那么让我们再想象一下进程内调用的一些条件。

大家知道静态链接(Windows的.lib和Linux的.a)和动态链接的主要目的是将可执行代码块链接到一起共同完成程序特定功能。静态链接在编译期完成,动态链接在程序执行过程中完成。动态链接技术主要解决程序在执行过程中如何通过特定的编址和寻址方式加载可执行代码(即如何将不连续的块组成我们前面谈到的冯•诺伊曼程式),所以一般从规范层面进行约定,即我们常说的动态链接库技术,在Windows(.dll)和Linux(.so)上都可以采用动态链接技术来设计程序,但它们的调用方式以及程序编制方式不尽相同(从这一点我们可以明白我们为什么叫Java本地接口了吧?原因是这些东东与操作系统千丝万缕)。如果我们要达到进程内调用的目的,看来只能通过动态链接技术了,而动态链接的前提条件是相互调用的模块都是可执行的。

《Java平台体系》——第二章 JVM——JNI(Java本地接口)_第1张图片

Windows下本地程序和Java程序互调

我们通过上图基本能明白JVM在互调中的中介作用,首先JVM是在操作系统上可执行的程序,所以满足和本地库互调的条件,同时JVM理解Java字节码,所以能够充当中介角色,JNI就是针对中介制定的一套约束,从而保证Java和本地代码之间的沟通顺畅。

:在.NET中与JNI可以类比的就是托管和非托管代码的概念。

通过前面我们自己的基本逻辑想象,我们不难概括出JNI的主要内容:
□ Java程序如何让JVM加载本地库,并且让对本地库的调用符合Java语法。
□ Java中的数据类型和本地库中的数据类型如何映射。
□ 本地程序如何让JVM调用Java程序,并且让对Java程序的调用符合本地库语法(例如C/C++)。

2.4.1 Java中使用本地库

我们从一段代码出发:

《Java平台体系》——第二章 JVM——JNI(Java本地接口)_第2张图片


上面代码通过native关键字告诉JVM该方法为本地方法,System.loadLibrary告诉JVM应该导入本地库pkg_Cls(在Windows环境下将查找pkg_Cls.dll,在Linux环境下将查找pkg_Cls.so)来填充navive关键字声明的方法。native和System.loadLibrary(当然Java还可通过RegisterNatives支持静态连接,这里不详细介绍)的组合告诉JVM将标注native的方法和导入库中的本地方法映射起来。这样对Cls.f方法的调用将不用关注更多的本地细节,仍然站在Java高级特性的高度。那JVM是如何将native标注的方法和本地库中的方法映射的呢?答案是通过命名约定。

命名约定如下:
□ 首先在方法前面加Java。
□ 然后用_分开,再加包名称。如果包名称中有非ASCII用_0XXXX形式的Unicode编码表示。
□ 然后用_分开,再加上方法名称。
□ 当出现1个以上函数名相同的时候,将用两个_将参数类型连接起来,参数类型的命名规则在JVM规范中有介绍,请大家参考JVM的规范文档。
□ 其中特殊字符_用_1代替,;用_2代替,[用_3代替。

例如上面的类Cls中的f方法将映射为:Java_pkg_Cls_f。

OK,这就是方法名称映射的约定。

我们再回到一个方法(很多人叫函数),一个方法一般有返回值、方法名称和参数三个要素,在JNI的一篇“最佳实践”文档(可以参考http://www.ibm.com/developerworks/cn/java/j-jni/​)中建议不要让本地代码反向和Java代码互动太多,即一般情况下Java调用本地库不建议本地代码反向调用Java代码,本地代码仅仅对方法参数进行理解和操作即可。因此,我们的重点是解释在本地代码中如何操作参数、数据类型映射。数据类型映射请参考小节数据类型映射,这里主要说一下本地代码如何操作参数,先看下图:

《Java平台体系》——第二章 JVM——JNI(Java本地接口)_第3张图片


前面我们说过,本地代码对Java代码的操作必须要经过JVM,特别是对引用对象的操作,上图中的JNIEnv就是JVM提供给本地代码的一个虚拟机Struct,即对引用对象的操作必须要经过JNIEnv。把这一点记在脑子里,在该章的实战​部分类似(*env)->Metod的调用你就明白什么意思。

其实Java调用本地代码的核心框架内容就这么多,在框架的指导下大家可以借助该章提供的参考资料和该章实战​部分开始动手了。

2.4.2 数据类型映射

如果你已经有了JDK,请在JDK目录中找到include/jni.h,它里面定义了所有JNI的数据类型。当然这需要你稍微懂一点C/C++的知识,对C/C++不了解但又感兴趣的朋友可以恶补一下相关知识,这对我们理解JNI是很有帮助的(在前言​部分提供过一个学习C的老掉牙教程)。

这块我不打算详细进行阐述,只要搞清楚值数据类型和引用数据类型的区别即可,同时在调用时注意j开头的C/C++数据类型具体对应C/C++的什么数据类型即可(可以通过JNI规范和jni.h获得对应关系),这是核心。在该章的实战​部分我会举个例子加深大家的理解(你可以跳着阅读实战​中的例子)。

2.4.3 本地程序如何调用虚拟机和Java代码

这是一个有趣的话题,但在我们前面阐述的基本思路框架下,我们理解这个并不难,除了一个反向的JVM建立之外,和前面说的内容基本是一致的。

用C/C++写程序的朋友只要知道本地程序调用虚拟机和Java代码都是通过动态链接技术先启动虚拟机,然后通过虚拟机调用Java代码便可动手写程序(当然也可以静态连接,唯一的前提条件就是要理解jni.h中定义的数据结构和每个方法的使用)。在该章的实战部分会给大家具体的例子。

你可能感兴趣的:(Java平台体系,java,jni,jvm,平台,虚拟机,windows)