1-Introduction

0x00、 介绍

本节介绍 Java Native Interface(JNI)。 JNI 是本地编程接口。 它允许运行在 Java Virtual Machine(JVM) 中的 Java 代码和用非 Java 编程语言(例如 C/C++ )写的应用程序和库进行交互。

JNI 最大的好处是它对底层的 Java VM 的实现没有任何限制。 因此 Java VM 的维护者能够在不影响 Java VM 其他部分的前提下增加对 JNI 的支持。 作为开发者我们能期望一个版本的 native 代码和所有支持 JNI 的 Java VM 一起运行。

接下来我们开始逐个小话题翻译:

0x01、 Java Native Interface 概述

你可以完全使用 Java 代码编写应用程序,但是有些情况纯粹的 Java 代码并不能满足你应用程序的需求。 当一个应用程序无法完全使用 Java 完成开发的时候,开发者可以使用 JNI 编写 Java native 方法去处理这些情况。

下面的例子说明了你需要 Java native mathod 的一些情况:

  • 标准 Java class 库不支持应用程序所需要的平台依赖功能。

  • 你已经有了用其他语言编写的库,希望通过 JNI 使 Java 代码访问它。

  • 在某些情况下你希望使用低级语言(例如汇编语言)实现一部分功能。

通过使用 JNI 编程,你能使用本地方法完成:

  • 创建,检查 和 更新 Java 代码(包括 数组 和 字符串)

  • 调用 Java 方法。

  • 捕获异常。

  • 加载类和获取类信息。

  • 执行运行时类型检查。

0x02、 历史背景

不同厂商虚拟机提供不同的本地方法。 这些不同的接口迫使开发人员为一个给定的平台编写,维护,发布不同版本的本地代码库。

我们对一些本地方法接口做简要的说明:

  • JDK 1.0 native mathod interface

  • Netscapes's Java Runtime Interface

  • Microsoft's Raw Native Interface and Java/COM interface

2.1、 JDK 1.0 Native Method Interface

JDK 1.0 的运行使用了本地方法接口。 不幸的是,由于以下两个主要原因这种做法未能在其他 Java VM 上得以应用:

  • FIRST, native 代码将 Java 对象中的属性当做 C 结构体成员进行访问。

    然而,java 语言规范没有定义对象在内存中的布局方式。

    如果一个 Java VM 在内存中有不同的布局,开发者将不得不重新编译本地方法库

  • SECOND, JDK 1的本机方法接口依赖于一个保守的垃圾收集器。

    例如,非限制宏的非限制使用使得有必要保守地扫描本机栈。

2.2、 Java Runtime Interface

网景公司提出的 Java Runtime Interface(JRI),一个为 Java 虚拟机提供服务的统一接口。 JRI 的设计考虑到了便携性——它对底层虚拟机中的实现细节做了很少的假设。 JRI 解决了大量的问题,包括:本地方法,调试,反射,嵌入(调用)等。

2.3、 Raw Native Interface and Java/COM Interface

Microsoft Java VM 支持两个本地方法接口。 在底层,它提供了一个高效的 Raw Native Interface(RNI)。 RNI提供了与JDK本机方法接口的高度源级向后兼容性,尽管它有一个主要区别。 和依赖保守的垃圾回收不同,这里本地方法必须使用 RNI 方法与垃圾回收器交互。

在更高层次上,Microsoft 的 Java/COM 接口提供了一个针对 Java VM 的标准独立语言二进制接口。 Java 代码能像使用 Java 对象一样使用 COM 对象。 一个 Java 类也能作为 COM 类暴露给系统其余部分。

0x03、 Objectives[目标]

我们一致相信,深思熟虑的标准接口能为所有人提供如下好处:

  • 每一个 VM 供应商能支持更大体量的本地代码。

  • 构建工具能不必维护不同种类的本地接口方法。

  • 应用程序将能一次编写到处(不同的 Java VM)运行。

实现一个标准本地方法接口的最好方法是使涉及的各方都对 Java VM 感兴趣。 因此我们组织了一系列有关于 Java 许可和本地接口一致性的讨论。 讨论确定了标准本地方法接口必须满足下述需求:

  • 二进制兼容 - 首要目标是在一个给定的平台上夸所有的 Java VM 实现本地方法库的二进制兼容。

    开发者能够做到开发一个版本的本地方方法库,该方法库能够在所有的平台运行

  • 高效 - 为了支持时间敏感的代码,本地方法接口必须开销足够小。

    用所有已知的技术确保虚拟机的独立性(因为二进制兼容)允许一部分开销。

    我们必须在有效性和独立性之前做出恰当的取舍。

  • 功能 - 必须暴露足够的 Java VM 内部接口去完成有用的任务。

0x04、 Java Native Interface Approach[JNI 方法]

我们希望采用某种已经存在的方法作为标准接口,这将样对开发者来说负担最小,因为开发者不得不针对不同的平台学习更多的接口。 不幸的是,不存在令人满意的解决方案。

网景的 JRI 作为本地方法接口的一部分是最接近我们想像的,它作为了我们设计的基础。 熟悉 JRI 将注意到在 API 命名约定,方法和属性 ID 的使用,以及本地和全局引用方面有相似支持。 尽管我们尽了最大努力, JNI 依然不能兼容 JRI(虽然 VM 同时支持 JNI 和 JRI)。

Microsoft 的 RNI 是 JDK 1.0 的改进版,因为他解决了本地方法和 GC 交互保守这个问题。 但是 RNI 作为独立的本地方法接口是不合适的。 和 JDK 相同,RNI 本地方法一访问 C 语言结构体的方式访问 Java 对象,将导致下面两个问题: 1. RNI 将 Java 对象的内部暴露给了本地方法。 2. 像访问 C 结构体一样访问 Java 对象不能有效的结合 “书写屏障”,但是这在新的垃圾回收算法中是必须的。

作为二进制标准,COM 确保完成在不同虚拟机间的二进制兼容。 调用一个 COM 方法只需要一个间接请求,这花费很少的资源。 再者,COM 对象对动态链接库有很好的改进,这些改进解决了版本控制问题。

使用 COM 作为标准 Java 本地接口方法会存在下面几个问题:

  • 一,Java/COM 接口缺乏某些被期望的方法,例如访问私有属性和触发一般的异常。

  • 二,Java/COM 接口默认为 Java 对象提供了独立的 IUnknown 和 IDispatch COM 接口,所以本地方法能狗访问 public 方法和属性。

    不幸的是,IDispatch 接口不处理 Java 重写方法,并且在匹配方法名的时候不区分大小写。

    此外,所有通过 IDispatch 接口 Java 方法的暴露都被强制执行动态类型检查。

    这是因为 IDispatch 接口设计的时候考虑的弱类型语言(例如 B 语言)。

  • 三,COM 的设计不是用于处理低级功能的,而是用于完成软件组件组合的(包括完善的应用程序)。

    我们相信将 Java 和低级的本地方法作为软件组件是不合适。

  • 四,即使采用了 COM ,它还缺乏对 UNIX 平台的支持。

虽然 Java 对象不像 COM 对象一样暴露给本地方法, JNI 接口本身与 COM 二进制兼容。 JNI 使用了与 COM 相同的跳转表结构和调用约定。 这意味着,只要能为 COM 提供跨平台支持,JNI 就能成为 Java VM 的 COM 接口。

JNI 不被认为是给定 Java VM 所支持的唯一本地方法接口。 一个标准的接口对开发者有好处,他们能够在不同的 Java 虚拟机中加载他们编写的库。 在一些情况下开发者必须使用低级的虚拟机指定的接口实现更高的效率。 在其他情况下,开发者应该使用高级接口构建软件组件。 确实,java 环境和软件组件技术愈加成熟,本地方法将逐渐失去它的意义。

0x05、 Programming to the JNI

本地方法开发者应该使用 JNI 编程。 面向 JNI 编程将你和未知情况解耦(未知情况例如,你不知道用户最终将程序运行在那个服务商的虚拟机)。 通过遵循JNI标准,您将为本机库提供在给定Java VM中运行的最佳机会。

如果你正在实现 Java VM ,你应该实现 JNI 。 JNI 已经多次测试并确保了在你实现虚拟机的时候 JNI 没有多余的开销和限制,包括对象表示,垃圾回收方案,等。 如果你在运行过程中发现任何我们忽视的问题,请反馈给我们。

0x06、 Changes

从Java SE 6.0开始,已删除不推荐使用的结构JDK1_1InitArgs和JDK1_1AttachArgs,而是使用JavaVMInitArgs和JavaVMAttachArgs。

Last updated