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 代理测试需要进行调整,以验证对静态链接代理库的支持。