JEP 178:静态链接的 JNI 库
概述
增强 JNI 规范以支持静态链接的本地库。
目标
-
修改 Java SE 规范和 JDK,使开发者能够将 Java 运行时、本地应用程序代码和 Java 应用程序代码打包为一个单一的二进制可执行文件,而无需使用共享的本地库。
-
无需对现有的 Java 代码进行任何更改即可使用静态本地库而不是动态本地库。特别是,形如
System.loadLibrary("foo")的方法调用应该能够加载"foo"库,无论该库是以静态形式还是动态形式提供的。 -
允许 Java 应用程序结合使用静态和动态本地库,但必须在尝试使用静态库之前将其加载到内存中。
-
允许 JVMTI Java 代理(Agents)选择性地与 Java 运行时静态链接。
非目标
对于转换为静态形式的现有动态原生库,保持完全的原生 C/C++ 源代码兼容性并不是目标。必须修改现有的 JNI_OnLoad 和 JNI_OnUnLoad 函数用法,以允许多个静态库共存。
动机
静态 JNI 库在以下两种主要场景中非常有用:
- 嵌入 JRE 的原生应用程序可能希望使用静态链接的 JNI 代码,而不是动态链接库。
- 在限制或不支持共享库的环境中运行的 Java 应用程序需要将 JRE 及其所有的原生 API 库代码链接到一个单一的可执行文件中。
另一个好处是,使用静态链接的 JNI 库时,目标文件链接器可以优化整个可执行文件,从而可能减少其大小。
描述
需要解决两个主要问题以增加对静态 JNI 库的支持:
-
当前启动动态库加载过程的 Java API 需要增强,以支持内置的静态库。使用静态 JNI 库的 Java 应用程序需要一种方法来通知虚拟机(VM),库代码已经包含在应用程序镜像中。在这种情况下,针对静态库的
System.loadLibrary请求应跳过通常的特定于平台的动态加载过程。当前的 JNI 规范提到了这种类型的支持,尽管 Hotspot 虚拟机并未实现该行为。
-
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 代理测试需要进行调整,以验证对静态链接代理库的支持。