跳到主要内容

JEP 178:静态链接的 JNI 库

QWen Max 中英对照

概述​

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

目标​

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

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

  3. 允许 Java 应用程序结合使用静态和动态本地库,但必须在尝试使用静态库之前将其加载到内存中。

  4. 允许 JVMTI Java 代理(Agents)选择性地与 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 虚拟机并未实现该行为。

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

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

Java API 变更​

java.lang.System.load 和 java.lang.Runtime.load 方法的规范将被修订为:

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

如果文件名参数在去掉任何平台特定的库前缀、路径和文件扩展名后,指示了一个名为 L 的库,并且一个名为 L 的本地库被静态链接到虚拟机(VM),那么将调用该库导出的 JNI_OnLoad_L 函数,而不是尝试加载动态库。与参数匹配的文件名不需要存在于文件系统中。更多细节请参阅 JNI 规范。

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

这些方法抛出 UnsatisfiedLinkError 的规范将被修订为:

UnsatisfiedLinkError - 如果文件名不是绝对路径名、本地库未与虚拟机进行静态链接,或者主机系统无法将该库映射为本地库镜像时抛出。

java.lang.System.loadLibrary 和 java.lang.Runtime.loadLibrary 方法的规范将被修订为:

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

如果名为 libname 的本地库是与虚拟机 静态链接 的,则会调用该库导出的 JNI_OnLoad_libname 函数。更多细节请参阅 JNI 规范。

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

这些方法抛出 UnsatisfiedLinkError 的规范将被修订为:

UnsatisfiedLinkError - 如果 libname 参数包含文件路径、本地库未与虚拟机进行静态链接,或者主机系统无法将该库映射为本地库镜像,则会抛出此错误。

JNI 规范变更​

  • 一个本地库可以与虚拟机(VM)进行静态链接。库和虚拟机镜像的结合方式取决于具体实现。
  • 必须通过 System.loadLibrary 或等效的 API 调用成功加载该库,才能认为该库已加载。
  • 如果且仅如果库 L 的镜像已经与虚拟机结合,并且该库导出了一个名为 JNI_OnLoad_L 的函数,则该库被定义为静态链接。
  • 如果一个静态链接的库 L 导出了名为 JNI_OnLoad_L 和 JNI_OnLoad 的两个函数,则 JNI_OnLoad 函数将被忽略。
  • 如果一个库 L 是静态链接的,则在第一次调用 System.loadLibrary("L") 或其等效方法时,会调用 JNI_OnLoad_L 函数,其参数和返回值预期与 JNI_OnLoad 函数的规定相同。
  • 如果一个库 L 是静态链接的,则禁止动态链接同名的库。
  • 当包含静态链接本地库 L 的类加载器被垃圾回收时,如果该库导出了 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 函数。文件系统中并不需要存在与参数匹配的库。此 Agent_OnLoad_L 函数将按照 JVMTI 规范中的描述被虚拟机调用。调用时,options 将被传递给 Agent_OnLoad_L 函数。

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

JVMTI -agentpath 命令行选项规范变更​

JDK 8 中将修订 -agentpath 命令行规范说明,内容将更改为:

如果 filename 参数在去掉任何平台特定的库前缀、路径和文件扩展名后,指示了一个名为 L 的 library,并且一个名为 L 的本地库被 静态链接 到 VM 中,那么代理必须导出 Agent_OnLoad_L 函数。与参数匹配的文件名不需要存在于文件系统中。此 Agent_OnLoad_L 函数将由虚拟机调用,具体描述见 JVMTI 规范。调用时,options 将传递给 Agent_OnLoad_L 函数。

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

JVMTI 本地接口规范变更​

  • 本地 JVMTI Agent 可以与虚拟机(VM)进行静态链接。库和虚拟机镜像的结合方式取决于具体实现。
  • 如果一个代理 L 的镜像已与虚拟机结合,则仅当该代理导出一个名为 Agent_OnLoad_L 的函数时,才将其定义为静态链接。
  • 如果一个静态链接的代理 L 导出了名为 Agent_OnLoad_L 和 Agent_OnLoad 的函数,则 Agent_OnLoad 函数将被忽略。
  • 如果代理 L 是静态链接的,则会调用 Agent_OnLoad_L 函数,并且其参数和预期返回值与 Agent_OnLoad 函数规定的相同。
  • 一个静态链接的代理 L 将禁止动态加载同名的代理。
  • 如果代理 L 导出了 Agent_OnUnload_L 函数,虚拟机将在启动过程中与调用动态入口点 Agent_OnUnLoad 相同的时间点调用该代理的 Agent_OnUnload_L 函数。
  • 如果一个静态链接的代理 L 导出了名为 Agent_OnUnLoad_L 和 Agent_OnUnLoad 的函数,则 Agent_OnUnLoad 函数将被忽略。
  • 如果代理 L 是静态链接的,则会调用 Agent_OnAttach_L 函数,并且其参数和预期返回值与 Agent_OnAttach 函数规定的相同。
  • 如果一个静态链接的代理 L 导出了名为 Agent_OnAttach_L 和 Agent_OnAttach 的函数,则 Agent_OnAttach 函数将被忽略。

com.sun.tools.attach.VirtualMachine.loadAgentLibrary​

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

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

com.sun.tools.attach.VirtualMachine.loadAgentPath​

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

如果代理是与 VM 静态链接的,否则该 VM 将加载它,那么根据 -agentpath JVMTI 规范的定义,调用的特定 Agent_OnAttach 函数名称将是库特定的。

JVMTI 版本规范将递增为 JDK18_JVMTI_VERSION。
JDK18_JVMTI_VERSION 将设置为 0x30010203,这相当于 1.2.3。

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

影响​

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