JNI - 设计概述
前言
本文是Oracle官方JNI文档的翻译基础上加上部分个人理解,原文地址 Chapter 2:Design Overview
本文介绍 JNI(Java Native Interface)的主要设计问题,这些问题很多跟Native方法相关,调用方法的介绍在JNI - 接口调用。
JNI最大的好处在于对底层JVM实现没有强制约束,所以JVM供应商可以在不影响其他JVM功能的基础上支持JNI。开发者可以开发一份Native应用或库,然后运行在所有支持JNI的JVM上。
JNI 接口函数及指针
Native代码通过JNI函数调用JVM的功能,JNI函数作为接口指针使用。接口指针是指针的指针,接口指针指向一组指针,其中每个指针指向一个接口函数,每个接口函数在指针数组中都有预定义的偏移。下图介绍接口函数指针的组织形式。
JNI接口以类似C++虚函数表和COM接口的形式组织。使用接口表而不使用硬连接条目的好处在于分离JNI命名空间及Native代码,VM可以提供几个版本的JNI函数表。比如VM可以提供两个JNI函数表:
- 一个用于非法参数检查,适用于调试
- 另一个执行最小的JNI规范检查,从而获取更高的效率
JNI接口指针只在当前线程有效,所以不能将JNI接口指针进行跨线程传递。VM的实现可能在JNI接口指针指向区域存放线程独立数据(thread-local data)。
Native方法以参数形式获取JNI接口指针,VM保证在同一线程中多次调用Native方法都会获得同一个JNI接口指针。但Native方法可能在不同的Java线程调用,所以Native方法可能会接收到不同的JNI接口指针。
编译、加载、链接Native方法
因为JVM是多线程的,Native库也应该使用多线程环境编译和链接。比如Sun Studio编译器编译C++代码应该使用 -mt 标志, 使用GNU gcc编译器应该使用 -D_REENTRANT 或 -D_POSIX_C_SOURCE。
Native方法通过 System.loadLibrary 方法加载。下述示例中,类初始化方法加载一个包含f方法的特定平台Native库:
package pkg;
class Cls {
native double f(int i, String s);
static {
System.loadLibrary(“pkg_Cls”);
}
}
传入System.loadLibrary方法的参数是开发者决定的Native库名称,传入参数会根据规矩生成平台特定的Native库名称。比如Solaris系统将pkg_Cls转化为libpkg_Cls.so,但在win32平台下会将pkg_Cls转化为pkg_Cls.dll。
开发者可使用一个Native库来存储任意数量类所需的Native方法,只需要这些类都是通过相同的类加载器加载。VM内部为每个类加载器维护了一个已加载Native库的列表。供应商应该选择Native库名称以尽可能减少命名冲突。
一个Native库可能被静态链接到VM,组合Native库和VM镜像的方法取决于具体的JVM实现。Native库只有在System.loadLibrary或同等API返回成功之后才被认为已加载。
当且仅当在Native库L暴露出一个名为 JNI_OnLoad_L 的函数时,Native库L才被视为和VM镜像静态链接。静态链接Native库同时暴露 JNI_OnLoad_L 和 JNI_OnLoad 函数时JNI_OnLoad 将被忽略,同时暴露 JNI_OnUnLoad_L 和 JNI_OnUnLoad 函数时JNI_OnUnLoad 将被忽略。
当静态Native库L第一次被System.loadLibrary(“L”)或同等API调用时,JNI_OnLoad_L将会使用跟JNI_OnLoad一致的参数及返回值被调用。若Native库已被静态链接,那么名称相同的动态库将无法链接。
开发者可以使用JNI函数 RegisterNatives() 注册native方法与类之间的关联,这在静态链接中非常实用。
解析Native方法
// todo