跳到主要内容

JEP 178:静态链接的 JNI 库

概括

增强 JNI 规范以支持静态链接的本机库。

目标

  1. 修改 Java SE 规范和 JDK,使开发人员能够将 Java 运行时、本机应用程序代码和 Java 应用程序代码一起打包到不需要使用共享本机库的单个二进制可执行文件中。

  2. 无需更改现有 Java 代码即可使用静态本机库(而不是动态本机库)。特别是,表单的方法调用System.loadLibrary("foo")应该能够加载"foo"库,无论该库是以静态还是动态形式提供的。

  3. 允许 Java 应用程序使用静态和动态本机库的组合,尽管静态库在尝试使用它们之前必须位于内存中。

  4. 允许 JVMTI Java 代理选择性地与 Java 运行时静态链接。

非目标

为转换为静态形式的现有动态本机库保留完整的本机 C/C++ 源兼容性并不是目标。JNI_OnLoad和函数的现有用法JNI_OnUnLoad需要进行修改,以允许多个静态库共存。

动机

静态 JNI 库在两种主要场景中很有用:

  1. 嵌入 JRE 的本机应用程序可能希望使用静态链接的 JNI 代码而不是动态链接的库。

  2. 在限制或不支持共享库的环境中运行的 Java 应用程序需要将 JRE 及其所有本机 API 库代码链接到单个可执行文件中。

另一个好处是,使用静态链接的 JNI 库,目标文件链接器可以优化整个可执行文件,从而有可能减小其大小。

描述

添加对静态 JNI 库的支持需要解决两个主要问题:

  1. 当前启动动态库加载过程的 Java API 需要增强以支持内置静态库。使用静态 JNI 库的 Java 应用程序需要一种方法来通知 VM 该库代码已包含在应用程序映像中。在这种情况下,System.loadLibrary对静态库的请求应该跳过通常的特定于平台的动态加载过程。

    当前的 JNI 规范暗示了这种类型的支持,尽管 Hotspot VM 并未实现该行为。

  2. 由于应用程序中只能存在单个函数名称,因此需要增强JNI_OnLoad和函数接口以支持库特定名称。JNI_OnUnload这可以通过将库名称附加到这些众所周知的名称来实现。例如libnet.so可以使用JNI_OnLoad_net, JNI_OnUnload_net

此功能需要更改 Java SE 库加载 API 和 JNI 规范。以下是这两个领域的规范更新的初步草案。

Java API 变更

的规范java.lang.System.loadjava.lang.Runtime.load方法将修改为:

加载由文件名参数指定的本机库。文件名参数必须是绝对路径名。

如果文件名参数在去除任何特定于平台的库前缀、路径和文件扩展名后指示名称为 L 的库,并且statically linkedVM 中有一个名为 L 的本机库,则调用该库导出的 JNI_OnLoad_L 函数而不是尝试加载动态库。与参数匹配的文件名不必存在于文件系统中。有关更多详细信息,请参阅 JNI 规范。

否则,文件名参数将以依赖于实现的方式映射到本机库映像。

这些方法何时抛出异常的规范UnsatisfiedLinkError将修改为:

UnsatisfiedLinkError- 如果文件名不是绝对路径名,则本机库不在statically linkedVM 中,或者主机系统无法将库映射到本机库映像。

的规范java.lang.System.loadLibraryjava.lang.Runtime.loadLibrary方法将修改为:

加载参数指定的本机库libname。不得libname包含任何特定于平台的前缀、文件扩展名或路径。

如果调用的本机库libnamestatically linkedVM 一起,则JNI_OnLoad_libname调用该库导出的函数。有关更多详细信息,请参阅 JNI 规范。

否则,libname将从系统库位置加载并以依赖于实现的方式映射到本机库映像。

这些方法何时抛出异常的规范UnsatisfiedLinkError将修改为:

UnsatisfiedLinkError- 如果 libname 参数包含文件路径,则本机库不在statically linkedVM 中,或者主机系统无法将库映射到本机库映像。

JNI 规范变更

  • 本机库可能statically linked与 VM 一起。库和 VM 映像的组合方式取决于实现。

  • 一个System.loadLibrary或等效的 API 调用必须成功,该库才会被视为已加载。

  • statically linked当且仅当库导出名为 JNI_OnLoad_L 的函数时,才定义其映像已与 VM 组合的库 L。

  • 如果statically linked库 L 导出一个名为 的函数JNI_OnLoad_L和一个名为 的函数JNI_OnLoad,则该JNI_OnLoad函数将被忽略。

  • 如果库 L 是statically linked在第一次调用System.loadLibrary("L")或等效函数时,则将使用为该JNI_OnLoad_L函数指定的相同参数和预期返回值来调用该JNI_OnLoad函数。

  • 如果库 L 是statically linked,则将禁止动态链接同名库。

  • 当包含本机库L的类加载器被垃圾收集时,如果导出了库的函数,则statically linkedVM将调用该函数。JNI_OnUnload_L

  • 如果statically linked库 L 导出一个名为 的函数JNI_OnUnLoad_L和一个名为 的函数JNI_OnUnLoad,则该JNI_OnUnLoad函数将被忽略。

JNI 版本规范将增加到JNI_VERSION_1_8.仅此版本或更高版本支持静态链接库。

JVMTI -agentlib 命令行选项规范更改

-agentlib 命令行规范描述将在 JDK 8 中修改为:

如果library参数指示名称为 L 的库,并且statically linkedVM 中有一个名为 L 的本机库,则Agent_OnLoad_L代理必须导出一个函数。与参数匹配的库不必存在于文件系统中。该Agent_OnLoad_L函数将由 VM 调用,如 JVMTI 规范中所述。调用时将options被传递给函数。Agent_OnLoad_L

否则,-agentlib: 后面的名称是要加载的库的名称。库的查找(包括其全名和位置)以特定于平台的方式进行。通常,agent-lib-name扩展为操作系统特定的文件名。将options在启动时传递给代理。例如,如果指定选项 -agentlib:foo=opt1,opt2,VM 将尝试从 WindowsTM 下的系统 PATH 加载共享库 foo.dll 或从 SolarisTM 操作环境下的 LD_LIBRARY_PATH 加载 libfoo.so。

JVMTI -agentpath 命令行选项规范更改

-agentpath 命令行规范描述将在 JDK 8 中修改为:

如果filename参数在去除任何特定于平台的库前缀、路径和文件扩展名后指示名称为 L 的名称,并且VM 中library有一个名为 L 的本机库,则该函数必须由代理导出。与参数匹配的文件名不必存在于文件系统中。该函数由 VM 调用,如 JVMTI 规范中所述。调用时将被传递给函数。statically linked``Agent_OnLoad_L``Agent_OnLoad_L``options``Agent_OnLoad_L

否则,-agentpath: 后面的路径是加载库的绝对路径。不会发生库名称扩展。将options在启动时传递给代理。例如,如果指定选项 -agentpath:/myLibs/foo.so=opt1,opt2,VM 将尝试加载共享库 /myLibs/foo.so。

JVMTI 本机接口规范更改

  • 本机 JVMTI 代理可能statically linked与 VM 一起使用。库和 VM 映像的组合方式取决于实现。

  • statically linked当且仅当代理导出一个名为 的函数时,Agent_OnLoad_L才定义其映像已与 VM 组合的代理 L。

  • 如果statically linked代理 L 导出一个名为 的函数Agent_OnLoad_L和一个名为 的函数Agent_OnLoad,则该Agent_OnLoad函数将被忽略。

  • 如果代理 L 是statically linkedAgent_OnLoad_L则将使用为函数指定的相同参数和预期返回值来调用函数Agent_OnLoad

  • 代理L将statically linked禁止动态加载同名代理。

  • Agent_OnUnload_L如果导出了代理的函数,VM 将在启动期间的同一点调用动态入口点Agent_OnUnLoad

  • 如果statically linked代理 L 导出一个名为 的函数Agent_OnUnLoad_L和一个名为 的函数Agent_OnUnLoad,则该Agent_OnUnLoad函数将被忽略。

  • 如果代理 L 是statically linkedAgent_OnAttach_L则将使用为函数指定的相同参数和预期返回值来调用函数Agent_OnAttach

  • 如果statically linked代理 L 导出一个名为 的函数Agent_OnAttach_L和一个名为 的函数Agent_OnAttach,则该Agent_OnAttach函数将被忽略。

com.sun.tools.attach.VirtualMachine.loadAgentLibrary

该语言将被添加到该方法的 javadoc 中:

如果代理statically linked与 VM 一起加载,则Agent_OnAttach调用的特定函数名称将是特定于库的,如 -agentlib JVMTI 规范部分中定义的那样。

com.sun.tools.attach.VirtualMachine.loadAgentPath

该语言将被添加到该方法的 javadoc 中:

如果代理statically linked与 VM 一起加载,则Agent_OnAttach调用的特定函数名称将是 -agentpath JVMTI 规范中定义的特定于库的函数名称。

JVMTI 版本规范将增加到 JDK18_JVMTI_VERSION。
JDK18_JVMTI_VERSION 将设置为 0x30010203,即 1.2.3。

支持 JDK18_JVMTI_VERSION 或更高版本的虚拟机将支持此新功能。

影响

  • 兼容性:此新功能不应影响现有的动态库。
  • 可移植性:静态构建时,JNI 本机源代码需要更改函数名称。
  • TCK:JNI 本机库测试需要进行调整,以验证对静态链接本机库的支持。
  • TCK:需要调整 JVMTI 代理测试以验证对静态链接代理库的支持。