第一章:JNI介绍

这个章节介绍Java Native Interface(JNI)。JNI是一套编程接口。这套接口可是使运行在Java虚拟机中的Java代码1和用其他语言编写的库文件(libraries)或者应用程序进行互操作(操作翻译为通信我觉得更恰当一点)。

JNI最重要的一点是,它对Java VM的JNI接口的实现没有强加任何限制2。因此呢,Java VM提供者可以在不影响他原来VM设计的情况下来增加对JNI的支持。

本章包括以下几个小节:

  • Java Native Interface 概述
  • JNI历史背景
    • JDK1.0 的本机接口方法
    • Java运行时接口
    • 原始本机接口和Java / COM接口
  • 目标

Java Native Interface 概述

虽然您可以用Java来编写程序,当时在有些情况下Java并不能满足程序的需要。程序员需要通过JNI来编写native方法3来处理这些情况。

以下示例将说明何时需要使用Java native方法:
* 标准的Java类库不支持应用所需的平台相关的功能的时候。
* 您已经有一个通过其他语言写好的类库,希望将它直接用到目前的Java项目中去的时候。
* 用Java代码来处理很耗时间而时间有很关键的时候(就是代码要求执行效率很高的时候,比如Android中处理bitmap时)。

在使用JNI编程过程中,JNI接口提供以下功能:

  • 创建、检查、操作Java对象(包括arrays和strings)。
  • 调用Java方法。
  • 捕获和跑出Java异常。
  • 加载Java类或者获取类信息。
  • 执行运行时类型检查。

你也可以用JNI的Invocation API4将Java VM嵌入到你的其他语言的程序用。这样将使程序员可以轻松的在现有的程序中使用Java的功能。

JNI历史背景

早期的时候,VMs的供应者们提供不同的本机方法接口来实现Java和其他语言的互通。这样将使开发者不得不在对应的Java VM平台来维护分发多个版本的JNI库实现。

我们简单的来看几个本机方法实现方案的例子:

  • JDK1.0本机方法接口
  • Netscape5 的Java Runtime Interface。
  • 微软的 Raw Native Interface and Java/COM interface。

JDK1.0 Native Method Interface

在JDK1.0中内置了一套本机方法接口。不幸的是,有两个原因使它不适合于其他的Java虚拟机。

首先,平台相关代码将 Java 对象中的域作为 C 结构的成员来进行访问。然而,Java语言规范并没有限制Java对象是如何在内存中布置的。如果Java VM在内存中布置对象的方式不同,程序开发者将不得不每次重新编译本机方法库。

第二点,JDK 1.0 的本机方法接口依赖于保守的垃圾收集器。例如,无限制地使用 unhand 宏使得有必要以保守方式扫描本机堆栈6

Netscape’s Java Runtime Interface
Netscape 提供了 Java 运行时接口 (JRI),一种 Java 虚拟机的通用接口。JRI 的设计融入了可移植性—它几乎没有对底层 Java 虚拟机的实现细节作任何假设。而且JRI 广泛讨论了各种各样的问题,包括本机方法、调试、反射、嵌入(调用)等等。

Raw Native Interface and Java/COM Interface
微软实现了一个Java虚拟机,其 Java 虚拟机实现了两种本机方法接口。在低一级,它提供了高效的原始本机接口 (RNI)。RNI 提供了与 JDK 本机方法接口有高度源代码级的向后兼容性,尽管它们之间还有一个主要区别,即平台相关代码必须用 RNI 函数来与垃圾收集器进行显式的交互,而不是依赖于保守的垃圾收集。
在高一级,微软的 Java/COM 接口为 Java 虚拟机提供了与语言无关的标准二进制接口。Java 代码可以象使用 Java 对象一样来使用 COM 对象。Java 类也可以作为 COM 类显示给系统的其余部分。

JNI的目标

我们相信,一个统一而且标准的接口将为每个人带来以下好处:、

  • 虚拟机提供者可以支持更多的本机代码(native code)。
  • 构建工具也不用再维护不同种类的本机方法接口(native method interfaces)。
  • 开发者只需要写一个版本的本机代码就可以在不同的VM上正确运行。

标准的本机方法接口的定义既然要兼容于不同的Java VM,就需要所有Java VM开发商或者个人或者对Java VM刚兴趣的都参与进来。因此,我们同所有的Java相关人员组织了一系列的讨论,来商议本机方法接口。讨论得到以下结论,要实现标准的本机方法接口,必须满足以下要求:

  • 二进制兼容性(Binary compatibility) - 二进制兼容性意思是说,在任何一个给定平台上的所有Java虚拟机必须兼容本地方法库(native method libraries)。7
  • 效率 - 为了支持时间敏感( time-critical)的代码,本地方法接口不能开销太大。综合目前所有已知技术来看,VM相对于JNI的独立性(和二进制兼容性)是本机方法产生开销的根本原因。所以某些情况下我们需要在VM的独立性和本地方法效率之间达成妥协。
  • 功能 - 这套接口必须将足够多的Java VM内部功能暴露出来,用以允许本机方法来执行所有需要的功能。

Java本地接口的最终实现(Approach)

我们希望采用一种现有的实现来作为我们的标准接口,因为这样将减轻开发者的负担。遗憾的是已有的解决方案中,没有一种完全满足我们的目标。

Netscape的JRI是最接近我们的目标的,因为我们将JRI作为参考进行设计。熟悉JRI的读者或许会注意到很多的相似之处。可惜尽管我们尽了最大的努力,但是JNI与JRI依然是不兼容的,但这并不妨碍VM同时提供对JNI和JRI的支持。

微软的RNI是对JDK 1.0中本地方法接口做了一些改进,解决了本机方法采用保守的垃圾回收的问题。但是RNI不适合作为VM堆里的本机方法接口。像JDK一样,RNI本地方法将Java Object当做C的结构体来直接访问,这将造成两个问题:

  • RNI将Java Object在VM中的内存布局暴露给了本地代码(每家VM的实现有可能采用不同的内存布局)。
  • 将 Java 对象作为 C 结构直接进行访问使得不可能有效地加入“写屏障”,写屏障是高级的垃圾收集算法所必需的(我也不明白垃圾回收和写屏障的关系)。

作为二进制标准,COM 确保了不同虚拟机之间的完全二进制兼容性。调用 COM 方法只要求间接调用,而这几乎不会占用系统开销。另外,COM 对象对动态链接库解决版本问题的方式也有很大的改进。

然而,有几个因素阻碍了将 COM 用作标准 Java 本地方法接口:

  • 第一,Java/COM 接口缺少某些必需功能,例如访问私有域和抛出普通异常。
  • 第二,Java/COM 接口自动为 Java 对象提供标准的 IUnknown 和 IDispatch COM 接口,因而平台相关代码能够访问公有方法和域。遗憾的是,IDispatch 接口不能处理重载的 Java 方法,而且在匹配方法名称时不区别大小写。另外,通过 IDispatch 接口暴露的所有 Java 方法被打包在一起来执行动态类型检查和强制转换。这是因为 IDispatch 接口的设计只考虑到了弱类型的语言(例如 Basic)。
  • 第三,COM 允许软件组件(包括完全成熟的应用程序)一起工作,而不是处理单个低层函数。我们认为将所有 Java 类或低层本地方法都当作软件组件是不恰当的。
  • 第四,在 UNIX 平台上由于缺少对 COM 的支持,所以阻碍了直接采用 COM。

虽然我们没有将 Java 对象作为 COM 对象暴露给平台相关代码,但是 JNI 接口自身与 COM 具有二进制兼容性。我们采用与 COM 一样的跳转表和调用约定。这意味着,一旦具有对 COM 的跨平台支持,JNI 就能成为 Java 虚拟机的 COM 接口。

我们认为 JNI 不应该是给定 Java 虚拟机所支持的唯一的本地方法接口。标准接口的好处在于程序员可以将自己的平台相关代码库加载到不同的 Java 虚拟机上。在某些情况下,程序员可能不得不使用低层且与虚拟机有关的接口来获得较高的效率。但在其它情况下,程序员可能使用高层接口来建立软件组件。实际上,我们希望随着 Java 环境和组件软件技术发展得越来越成熟,本地方法将变得越来越不重要。(此部分为直接参考别人blog,真的懒得翻译)。

使用JNI进行编程

本地方法的编写者应该利用JNI进行编程,这样将隔离开一些未知情况,比如中断正在使用的哪一个厂商的VM。通过使用JNI,你的本机代码库在任何给定的Java VM中将会有一个更好的运行环境。

如果你是Java VM提供者,你应该支持JNI。经过时间的验证,JNI不会对您的VM实现造成任何的开销或者限制,包括对象的标示,垃圾回收方案等。如果遇到任何我们没有注意到的问题,您可以随时反馈给我们。

改变

从Java SE6.0开始,移除了已经弃用的结构体JDK1_1InitArgs和JDK1_1AttachArgs,使用JavaVMInitArgs和JavaVMAttachArgs代替。

[1] 其他语言:一般来说只有C、C++、汇编。事实上只要能提供通过C代码来调用的接口就可以。
[2] 这句话看起来或许会使大家感到迷茫,它的意思是说,JNI只是Sun公司定义的一套接口规范,对各家Java VM的实现没有限制。
[3] 在.java文件中定义方法前面加native关键字,以及其所对应的C方法,即为native方法。
[4] JNI中的一套接口,第五章会有翻译。
[5] 网景公司,曾经很厉害的一家公司。
[6] JDK 1.0 的本机方法接口依赖于保守的垃圾收集器。例如,无限制地使用 unhand 宏使得有必要以保守方式扫描本机堆栈。
[7] 这句话或许有点难以理解,翻译的也不是很好,简单点说,就是在一个平台可以使用的已经编译好(编译为二进制文件)的本机方法在其他平台必须也可以使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章