JNI之一:JNI介绍

JNI(Java Native Interface),即Java本地程序接口。它允许Java代码在JVM内部与其它语言,如C、C++或汇编语言编写的程序或库直接交互。

JNI最大重要的好处是它对底层JVM的实现没有任何限制。因此,JVM厂商可以添加对JNI的支持,但不影响虚拟机的其它部分。程序员只写一种版本的本地程序,并期望它可以在所有支持JNI的虚拟机上使用。

JNI概述

虽然应用程序完全可以使用Java编写,但是某些情况下仅使用Java是无法满足应用程序需求的。程序员使用JNI编写Java本地方法来处理那些仅使用Java完成不了的情况

以下示例说明了何时需要使用Java本地方法:

  • 传统的Java类库不支持应用程序所需的平台相关的功能
  • 已有一个已经通过其他语言编写好的现成的库,你希望可以通过JNI在Java中使用它
  • 你希望用低级语言(如汇编)实现一小部分time-critical(时间要求苛刻)的代码。

通过JNI编程,可以:

  • 创建、检查、更新Java对象(包含数组和字符串)
  • 调用Java方法
  • 捕获并抛出异常
  • 加载类并获取类信息
  • 执行运行时类型检查

可以将JNI与Invocation API结合使用,使任意的native程序嵌入到JVM中。这使程序员可以轻松的使其现有的应用程序启用Java功能,而不必连接VM源码。

历史背景

不同厂商的虚拟机提供了不同的本地方法接口。不同的接口迫使程序员在给定的平台上生产、维护及分发多个版本的本地方法库。

我们要检查一些本地方法接口,比如:

  • JDK 1.0 本地方法接口
  • 网景(Netscape)的JRI(Java Runtime Interface)
  • 微软(Microsoft)的RNI(Raw Native Interface)及Java/COM接口

JDK 1.0 本地方法接口

JDK1.0已经附带了本地方法接口,不幸的是,有两个主要原因导致该接口不适合其他Java VM使用。

第一,native代码方法将Java对象中的字段作为C结构体中的成员。然而,Java语言规范中并没有为对象在内存中如何布局做定义。如果对象在JVM内存中布局不同,程序员需要重新编译本地方法库。

第二,JDK1.0的native方法以来一个保守的垃圾收集器。无限制的使用unhand宏,比如,保守的扫描本地堆栈。

Java运行时接口

网景提议过Java运行时接口(JRI),JVM为服务提供的一个通用接口。JRI在设计时考虑了移植性——它对底层Java VM中的实现细节进行了一些假设。JRI解决了一系列问题,包括native方法、调试、反射、嵌入(调用)等等。

原始本地接口及Java/COM 接口

微软JVM支持两个native方法接口。

在底层,提供一个高效的RNI(Raw Native Interface,RNI)。虽然有很大的区别,但还是RNI提供一个与JDK的native方法接口高度的源代码级向后兼容性。而不依赖保守垃圾收集,native代码必须使用RNI函数直接与垃圾收集器交互。

在上层,微软的Java/COM接口为JVM提供了一个语言无关的标准的二进制接口。Java代码可以像使用Java对象一样使用COM对象。Java类也可以作为COM类公开给系统的其余部分。

目标

统一的、深思熟虑的标准接口将提供以下便利:

  • 每个虚拟机厂商可以支持大量的本地代码
  • 工具构建者无需维护不同类型的本地方法接口
  • 应用开发者能够只需写一个版本的本地代码,且这个版本的代码可以运行在不同的虚拟机上。

实现标准本地方法接口的最佳方法是让对Java VM感兴趣的各方参与。此前,官方在Java被许可方之间关于统一本地方法接口组织了一系列的讨论。通过讨论,明确了标准本地方法接口必须满足以下要求:

  • 二进制兼容性——主要目标是本地方法库在给定平台上的所有JVM实现之间的二进制兼容性。程序员只需为给定的平台维护一个版本的本地方法库。
  • 效率——支持时间关键代码(time-critical),本地方法接口必须实现很少的开销。确保VM独立性(从而保证二进制兼容性)的所有已知技术都会带来一定的开销。我们必须以某种方式在效率和虚拟机独立性之间达成妥协。
  • 功能性——该接口必须公开足够的Java VM内部组件,以允许本机方法完成有用的任务。

JNI方法

Java希望采用现有方法之一作为标准接口,因为这将使必须学习不同VM中的多个接口的程序员的负担最小。不幸的是,现有的解决方案没能够完全令人满意地实现这一目标。

网景的JRI是与设想的可移植本地方法最接近的,被用作设计的起点。熟悉JRI的读者会注意到相似之处,API的命名,方法和字段ID的使用,局部引用和全局引用的使用,等等。虽然Java尽量最大努力使虚拟机支持JRI和JNI,但JNI并不与JRI二进制兼容。

微软的RNI是对JDK1.0的改进,因为它解决了本地方法与非保守垃圾收集器组合工作的问题。但RNI并不是虚拟机独立的本地方法接口。RNI本地方法将Java对象作为C结构体进行访问,从而导致两个问题:

  • RNI向native代码暴露了Java对象的内部布局。
  • 直接将Java对象作为C结构体访问,使其无法有效地合并“write barriers”,这是高级垃圾收集算法所必须的。

最为二进制标准,COM确保不同虚拟机之间的完全二进制兼容性。调用COM方法只需一个开销极小的间接调用。此外,在解决版本控制问题方面,COM对象是对动态链接库的重大改进。

将COM作为标准Java本地方法接口的使用受到一些因素的限制:

  • 首先,Java/COM接口缺乏某些所需的功能,如访问私有字段,提供通用异常。
  • Java/COM接口向Java对象自动提供了标准的IUnkown和IDispatch COM接口,因此native代码可以访问公共方法和字段。不幸的是,IDispatch接口不处理重载的Java方法,并且在匹配方法名称时不区分大小写。此外,通过IDispatch接口公开的所有Java方法都被包装以执行动态类型检查和强制转换。这是由于IDispatch接口在设计时考虑了弱类型的语言(如Basic)。
  • COM旨在允许软件组件(包括成熟的应用程序)一起工作,而不是处理单个的低级功能。将所有Java类或底层本地方法都作为软件组件来对待,显然是不合适的
  • 由于缺少UNIX平台上的COM支持,因此COM方案立即采用受到了阻碍

尽管Java对象不作为COM对象暴露给native代码,但JNI本身与COM二进制兼容。JNI使用与COM相同的跳表结构和调用约定。这意味着,一旦获得对COM的跨平台支持,JNI就可以成为Java VM的COM接口

不认为JNI是给定JVM支持的唯一本地方法接口。标准接口使程序员受益,他们希望将其本机代码库加载到不同的Java VM中。在某些情况下,程序员可能必须使用底层的,特定的VM接口才能获得高效率。在其他情况下,程序员可能使用高级别的接口来构建软件组件。的却是这样,Java环境和组件软件技术日趋成熟,本地方法将逐渐失去其意义。

JNI编程

本地方法程序员应该使用JNI进行编程。JNI编程使你和用户的虚拟机之间透明,遵守JNI标准,是使native库运行在JVM上的最佳选择。如果你正在实现一个Java虚拟机,那么你就应该实现JNI,JNI经过时间检验且确保不在你的Java虚拟机上增加任何开销和限制,包括对象表现,垃圾收集策略等等。