Skip to content

JEP 178: Statically-Linked JNI Libraries | 静态链接的 JNI 库

摘要

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

目标

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

  2. 在使用静态本地库与动态本地库相比时,不要求对现有 Java 代码进行任何更改。特别是,形式为 System.loadLibrary("foo") 的方法调用应该能够加载 "foo" 库,无论该库是以静态形式还是动态形式提供。

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

  4. 允许 JVMTI Java 代理(Agents)可选地与 Java 运行时静态链接。

非目标

对于转换为静态形式的现有动态本地库,不保证完全保留本地 C/C++ 源代码的兼容性。为了允许多个静态库共存,现有使用 JNI_OnLoadJNI_OnUnload 函数的情况需要进行修改。

动机

静态 JNI 库在以下两种主要场景中非常有用:

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

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

作为附加好处,使用静态链接的 JNI 库时,对象文件链接器可以优化整个可执行文件,从而可能减小其大小。

描述

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

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

    当前的 JNI 规范 暗示了这种支持,但 Hotspot VM 并未实现这种行为。

  2. 需要增强 JNI_OnLoadJNI_OnUnload 函数接口以支持库特定名称,因为应用程序中只能存在一个函数名称。这可以通过在这些众所周知的名称后附加库名来实现。例如,libnet.so 可以使用 JNI_OnLoad_netJNI_OnUnload_net

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

Java API 变更

java.lang.System.loadjava.lang.Runtime.load 方法的规范将修订为以下内容:

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

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

否则,文件名参数将以与实现相关的方式映射到本地库映像。

这些方法何时抛出 UnsatisfiedLinkError 的规范将修订为以下内容:

UnsatisfiedLinkError - 如果文件名不是绝对路径名,本地库没有与 VM “静态链接” ,或者主机系统无法将库映射到本地库映像。

java.lang.System.loadLibraryjava.lang.Runtime.loadLibrary 方法的规范将修订为以下内容:

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

如果名为 libname 的本地库与 VM “静态链接” ,则调用该库导出的 JNI_OnLoad_libname 函数。有关更多详细信息,请参阅 JNI 规范。

否则,将从系统库位置加载 libname,并以与实现相关的方式映射到本地库映像。

这些方法何时抛出 UnsatisfiedLinkError 的规范将修订为以下内容:

UnsatisfiedLinkError - 如果 libname 参数包含文件路径,本地库没有与 VM “静态链接” ,或者主机系统无法将库映射到本地库映像。

JNI 规范变更

  • 本地库可以与 VM “静态链接” 。库和 VM 映像的组合方式取决于实现。

  • 对于此库,必须成功调用 System.loadLibrary 或等效的 API,才能认为它已加载。

  • 如果库 L 的映像已与 VM 组合,则当且仅当库导出名为 JNI_OnLoad_L 的函数时,该库才定义为 “静态链接” 。

  • 如果 “静态链接” 的库 L 导出名为 JNI_OnLoad_L 的函数和名为 JNI_OnLoad 的函数,则 JNI_OnLoad 函数将被忽略。

  • 如果库 L 是 “静态链接” 的,则在首次调用 System.loadLibrary("L") 或等效调用时,将使用与 JNI_OnLoad 函数相同的参数和预期返回值调用 JNI_OnLoad_L 函数。

  • 如果库 L 是 “静态链接” 的,则禁止动态链接同名的库。

  • 当包含 “静态链接” 本地库 L 的类加载器被垃圾回收时,如果库导出了 JNI_OnUnload_L 函数,VM 将调用该库的 JNI_OnUnload_L 函数。

  • 如果 “静态链接” 的库 L 导出名为 JNI_OnUnLoad_L 的函数和名为 JNI_OnUnLoad 的函数,则 JNI_OnUnLoad 函数将被忽略。

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

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

在 JDK 8 中,-agentlib 命令行规范描述将修订为以下内容:

如果 library 参数指示的库名为 L,并且名为 L 的本地库与 VM “静态链接” ,则代理必须导出 Agent_OnLoad_L 函数。与参数匹配的库不必存在于文件系统中。VM 将按照 JVMTI 规范中所述调用此 Agent_OnLoad_L 函数。调用时将把 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 命令行选项规范变更

在 JDK 8 中,-agentpath 命令行规范描述将修订为以下内容:

如果去除任何特定于平台的库前缀、路径和文件扩展名后,filename 参数指示的库名为 L,并且名为 L 的本地库与 VM “静态链接” ,则代理必须导出 Agent_OnLoad_L 函数。与参数匹配的文件名不必存在于文件系统中。VM 将按照 JVMTI 规范中所述调用此 Agent_OnLoad_L 函数。调用时将把 options 传递给 Agent_OnLoad_L 函数。

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

JVMTI 本地接口规范变更

  • 本地 JVMTI 代理可以与 VM “静态链接” 。库和 VM 映像的结合方式取决于实现。

  • 如果与 VM 结合的代理 L 映像导出名为 Agent_OnLoad_L 的函数,则该代理被定义为 “静态链接” 。

  • 如果 “静态链接” 的代理 L 导出名为 Agent_OnLoad_LAgent_OnLoad 的函数,则 Agent_OnLoad 函数将被忽略。

  • 如果代理 L 是 “静态链接” 的,则将以与 Agent_OnLoad 函数相同的参数和预期返回值调用 Agent_OnLoad_L 函数。

  • “静态链接” 的代理 L 将禁止加载同名的动态代理。

  • 如果代理导出了名为 Agent_OnUnload_L 的函数,VM 将在与调用动态入口点 Agent_OnUnload 相同的启动点调用该函数。

  • 如果 “静态链接” 的代理 L 导出名为 Agent_OnUnload_LAgent_OnUnload 的函数,则 Agent_OnUnload 函数将被忽略。

  • 如果代理 L 是 “静态链接” 的,则将以与 Agent_OnAttach 函数相同的参数和预期返回值调用 Agent_OnAttach_L 函数。

  • 如果 “静态链接” 的代理 L 导出名为 Agent_OnAttach_LAgent_OnAttach 的函数,则 Agent_OnAttach 函数将被忽略。

com.sun.tools.attach.VirtualMachine.loadAgentLibrary

将向此方法的 javadocs 中添加以下语言:

如果代理与否则将加载它的 VM “静态链接” ,则调用的特定 Agent_OnAttach 函数名称将如 -agentlib JVMTI 规范部分中定义的那样特定于库。

com.sun.tools.attach.VirtualMachine.loadAgentPath

将向此方法的 javadocs 中添加以下语言:

如果代理与否则将加载它的 VM “静态链接” ,则调用的特定 Agent_OnAttach 函数名称将如 -agentpath JVMTI 规范中定义的那样特定于库。

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

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

影响

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