JNI使用:设计综述

JNI接口函数以及指针示意图:


可以看到,JNI接口设计类似于c++的虚函数表或者COM接口。使用函数表而不用硬连接函数入口(hard-wired function entries)的方式的好处有:JNI命名空间与native代码相隔离。VM能够简单的提供多个版本的JNI函数表。例如,VM可能支持两个JNI函数表:

1. 其中一个执行参数非法检查,另外一个用来调试;

2.其中一个执行JNI指定的最小的检查,因此更加有效。

JNI接口指针只有在当前线程中有效。因此native方法不能传递一个接口指针到另外一个线程的方法中。由JNI接口指针指向的区域里的本地线程数据可能由VM实现的JNI分配和保存。

native方法的参数可以为JNI接口指针。在同一个java线程中,当VM调用多次调用native方法,VM保证传递相同的接口指针给该native方法。然而,native方法能够被不同的java线程调用,因此native方法可能接收到不同的JNI接口指针。

加载和连接native方法

native方法可以通过System.loadLibrary方法加载。在下面的例子中,该类的初始化方法加载了平台相关的native库,其中native 方法f定义在native库中:

package pkg;  

class Cls { 

     native double f(int i, String s); 

     static { 

         System.loadLibrary(“pkg_Cls”); 

     } 

} 

System.loadLibrary的参数是由编程人员自主选择的库名字。系统符合标准,但与平台相关的,能够将库的名字转换为native库名。例如,Solaris系统将pkg_Cls转换为libpkg_Cls.so,然而win32系统将pkg_Cls转换为pkg_Cls.dll。

编程人员可以采用单个library来存储由任何数量的类所需的所有的native方法,只要这些类能够加载到同一个class loader里面。VM内部为每个class loader维护一系列已经加载的native libraries。

如果运行的操作系统不支持动态链接,所有的native 方法都必须提前链接到VM。在这种情况下,VM不需要实际加载库就可以完成System.loadLibrary调用。

编程人员同样可以调用JNI方法来RegisterNatives()注册native方法,使之与类想联系。RegisterNatives()函数对于静态链接方法有用。


解决native方法名:

动态链接器通过名字来决议,native方法名由下面组件来串联:

1.java前缀;

2. a mangled fully-qualified class name

3. 下划线分隔

4. for overloaded native methods, two underscores (“__”) followed by the mangled argument signature

VM检查方法名是否与处于远端的native library中的方法匹配: VM检查short name,该名字不需要参数签名;接着检查long name,该名字需要参数签名。


采用简单的名字交叉方法来确保所有的unicode字符能够转换为有效的C函数名字。我们在所有合格的类名中使用下划线“_”字符来取代"/"。因为名字以及类型描述符不能由数字开始,对于换码顺序我们可以用_0,_1,....,_9,如下表所示:

Table 2-1 Unicode Character Translation
Escape Sequence
Denotes
_0XXXX
a Unicode character XXXX
Note that lower case is used 
to represent non-ASCII 
Unicode characters, e.g., 
_0abcd as opposed to 
_0ABCD.
_1
the character “_”
_2
the character “;” in signatures
_3
the character “[“ in signatures


所有的native方法以及API接口都遵循给定平台下的标准库调用习惯,UNIX系统采用C调用习惯,然而Win32系统采用_stdcall。


Java对象引用:

对于基本类型,例如integers,characters等,Java和native代码可以复制。而自定义的java对象,只能传递引用。VM必须跟踪传递给native代码的所有对象,只有这样这些对象才能通过垃圾回收器释放。因此,native代码必须有一种方法通知VM,它不再需要这些对象。除此之外,垃圾回收器必须能够移除由native代码引用的对象。

全局和局部引用:

JNI将对象引用分为局部引用和全局引用两类。局部引用对native方法调用的一段时间内有效,而且在native方法返回时自动释放;而全局引用直到显示释放才失效。

所有由JNI函数返回的Java对象都是局部引用,但是JNI允许编程人员将局部引用创建为全局引用。JNI函数希望Java对象能够接收两种引用。native方法根据VM的结构返回局部或者全局引用。

在大多数情况下,当natie方法返回时,编程人员可以依赖VM开释放局部引用。然而,有的时候需要编程人员显式的释放局部引用。例如:

1.native方法访问一个很大的java对象,因此需要创建一个局部引用给该java对象。该native方法在返回之前需要执行而外的计算,大的java对象的局部引用将阻止对象被gc,尽管该对象在计算中不需要。

2. native方法创建了不需要同时使用但是是大量的局部引用。因为VM需要一定的空间来保持局部引用的跟踪而创建太多的局部引用会导致系统的内存消耗殆尽。例如,native方法通过大数组对象来进行循环,在每一次迭代中操作一个元素。每次迭代之后,编程人员不再需要该元素的局部引用。

JNI允许编程者在native方法的任何地方手动的delete局部引用。为了确保编程者手动释放局部引用,JNI函数不允许创建额外的局部引用,除非该引用作为结果返回。

实现局部引用:

为了实现局部引用,Java VM创建了一个登记处(registry)用来控制java到native方法的转换。登记映射表(registry Map)不能移动局部引用到java对象,保持java对象不能被垃圾回收。所有传递给native方法的java对象(包括作为JNI函数调用返回的结果)都自动的加载到registry中。当native返回时,registry将会被删除,从而允许它的入口可以被垃圾回收。

表,链表或者hash表等都可以用来实现registry。尽管引用计数可以用来避免registry重复的登记,但是JNI实现不需要检测以及删除重复的登记。


访问方法和属性:

native访问java的方法和属性必须确保:对要访问的类保持有效的引用;或者重新计算方法和属性的ID。

JNI不能添加限制在方法和属性ID的内部实现上。


报告编程errors:

JNI不会检查类似与NULL指针或者非法参数类型的编程错误。非法的参数类型包括正常的java对象,以及java类对象。JNI不会检查这些错误是因为:

1. 强迫JNI函数来检查所有的可能出现的错误会降低正确的native方法的性能;

2. 在很多情况下,没有足够的运行时类型信息来完成这类检查。


Java异常:

JNI允许native方法抛出自定义的java异常。native代码可能可以解决java异常。而未解决的java异常可能会传递给VM。

异常和错误码:

特定的JNI函数使用java异常机制来报告错误情况。在大部分情况,JNI函数通过返回错误码以及抛出java异常来报告错误。错误码通常是返回值,因此编程者可以:

1.快速检查上一个JNI调用的返回值,确定是否有错误出现;

2. 调用函数 ExceptionOccurred(),来获得包含了更多具体错误情况描述的exception对象。
在下面的两种情况下,编程者需要检查异常而不是首先坚持error code:

1. 调用java方法的JNI函数返回java方法的结果。变成者必须调用ExceptionOccurred()来检查在执行java方法中可能的异常;

2. 一些JNI的数组接入不能返回错误码,但是会扔出 ArrayIndexOutOfBoundsException or ArrayStoreException.

异步异常:

在多线程中,非当前线程可能会post一个异步的异常。异步的异常不会立即影响当前线程中native代码的运行,直到:

1. native代码调用了其中一个可能会导致同步异常的JNI函数;

2. native代码使用ExceptionOccurred()来显式检查同步或者异步异常。

native放大需要在必要的地方插入ExceptionOccurred()来确保当前线程在合理的时间范围内响应异步异常。

异常处理:

在native代码中有两种方法来处理异常:

1.native代码选择立即返回,因为异常是由native代码调用的java代码中扔出;

2. native代码能够通过调用ExceptionClear()来清除异常,而且执行自己的异常处理代码。

一旦有异常出现,nativa代码必须在调用JNI前清除异常。当有异常pending,只有这些JNI函数 ExceptionOccurred(), ExceptionDescribe(), 和 ExceptionClear()是安全的。 ExceptionDescribe()函数打印关于异常pending的调试信息。

展开阅读全文

没有更多推荐了,返回首页