跳到主要内容

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_OnLoadJNI_OnUnLoad 函数用法,以允许多个静态库共存。

动机

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

  1. 嵌入 JRE 的原生应用程序可能希望使用静态链接的 JNI 代码,而不是动态链接库。
  2. 在限制或不支持共享库的环境中运行的 Java 应用程序需要将 JRE 及其所有的原生 API 库代码链接到一个单一的可执行文件中。

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

描述

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

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

    当前的 JNI 规范提到了这种类型的支持,尽管 Hotspot 虚拟机并未实现该行为。

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

java.lang.System.loadLibraryjava.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_LJNI_OnLoad 的两个函数,则 JNI_OnLoad 函数将被忽略。
  • 如果一个库 L 是静态链接的,则在第一次调用 System.loadLibrary("L") 或其等效方法时,会调用 JNI_OnLoad_L 函数,其参数和返回值预期与 JNI_OnLoad 函数的规定相同。
  • 如果一个库 L 是静态链接的,则禁止动态链接同名的库。
  • 当包含静态链接本地库 L 的类加载器被垃圾回收时,如果该库导出了 JNI_OnUnload_L 函数,虚拟机将调用此函数。
  • 如果一个静态链接的库 L 导出了名为 JNI_OnUnLoad_LJNI_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_LAgent_OnLoad 的函数,则 Agent_OnLoad 函数将被忽略。
  • 如果代理 L 是静态链接的,则会调用 Agent_OnLoad_L 函数,并且其参数和预期返回值与 Agent_OnLoad 函数规定的相同。
  • 一个静态链接的代理 L 将禁止动态加载同名的代理。
  • 如果代理 L 导出了 Agent_OnUnload_L 函数,虚拟机将在启动过程中与调用动态入口点 Agent_OnUnLoad 相同的时间点调用该代理的 Agent_OnUnload_L 函数。
  • 如果一个静态链接的代理 L 导出了名为 Agent_OnUnLoad_LAgent_OnUnLoad 的函数,则 Agent_OnUnLoad 函数将被忽略。
  • 如果代理 L 是静态链接的,则会调用 Agent_OnAttach_L 函数,并且其参数和预期返回值与 Agent_OnAttach 函数规定的相同。
  • 如果一个静态链接的代理 L 导出了名为 Agent_OnAttach_LAgent_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 代理测试需要进行调整,以验证对静态链接代理库的支持。