Native Image 兼容性指南

Native Image 编译 Java 应用程序的方式与传统 Java 虚拟机 (VM) 不同。它区分了构建时运行时。在镜像构建时,native-image 构建器执行静态分析,以查找应用程序入口点可访问的所有方法。然后,构建器将这些(且仅这些)方法编译成一个可执行二进制文件。由于这种不同的编译模型,Java 应用程序在编译为原生镜像时行为可能有所不同。

Native Image 提供了一种优化,可减少应用程序的内存占用和启动时间。这种方法依赖于“封闭世界假设”,其中所有代码在构建时都是已知的。也就是说,在运行时不会加载新代码。与大多数优化一样,并非所有应用程序都适用于这种方法。如果 native-image 构建器无法在构建时优化应用程序,它会生成一个所谓的“回退文件”,该文件需要 Java VM 才能运行。我们建议查看Native Image 基础,以详细了解您的 Java 应用程序在构建时和运行时的行为。

需要元数据的功能 #

为了适应封闭世界假设,以下 Java 功能通常需要元数据在构建时传递给 native-image。此元数据可确保原生镜像使用所需的最小空间量。

通过在 GitHub 上发布共享可达性元数据,Native Image 与最流行的 Java 库的兼容性最近得到了增强。用户可以分担维护第三方依赖项元数据的负担并重用它。请参阅可达性元数据了解更多信息。

与封闭世界假设不兼容的功能 #

一些 Java 功能在封闭世界假设下尚不支持,如果使用,将导致生成回退文件。

invokedynamic 字节码和方法句柄 #

在封闭世界假设下,所有被调用的方法及其调用站点都必须是已知的。invokedynamic 方法和方法句柄可以在运行时引入调用或更改被调用的方法。

请注意,由 javac 生成的 invokedynamic 用例,例如 Java lambda 表达式和字符串拼接,是受支持的,因为它们在运行时不会更改被调用的方法。

在原生镜像中可能操作不同的功能 #

Native Image 实现了一些 Java 功能,其方式与 Java VM 不同。

安全管理器 #

java.lang.System#getSecurityManager() 始终返回 null,即使在启动时通过 -Djava.security.manager 设置了安全管理器。

如果程序启动时 -Djava.security.manager 被设置为除 disallow 以外的任何值,则使用非空参数调用的 java.lang.System#setSecurityManager(SecurityManager) 将抛出 java.lang.SecurityException

信号处理器 #

注册信号处理器需要启动一个新线程来处理信号并调用关机钩子。默认情况下,在构建原生镜像时不会注册信号处理器,除非用户明确注册。例如,在构建共享库时不建议注册默认信号处理器,但在为容器化环境(如 Docker 容器)构建原生可执行文件时,包含信号处理器是可取的。

要注册默认信号处理器,请将 --install-exit-handlers 选项传递给 native-image 构建器。此选项为您提供与 Java VM 相同的信号处理器。

类初始化器 #

默认情况下,类在运行时初始化。这确保了兼容性,但限制了一些优化。为了更快的启动和更好的峰值性能,最好在构建时初始化类。类初始化行为可以使用选项 --initialize-at-build-time--initialize-at-run-time 来指定特定类和包或所有类。JDK 类库中的类默认初始化。

注意:在构建时初始化类可能会破坏现有代码中的特定假设。例如,在类初始化器中加载的文件在构建时可能与运行时不在同一位置。此外,某些对象(如文件描述符或运行中的线程)不得存储在原生可执行文件中。如果在构建时可访问此类对象,则 native image 构建器将失败并报错。

更多信息,请参阅Native Image 中的类初始化

终结器 #

Java 基类 java.lang.Object 定义了 finalize() 方法。当垃圾回收器确定没有更多引用指向某个对象时,它会在该对象上调用此方法。子类可以覆盖 finalize() 方法以释放系统资源或执行其他清理操作。

自 Java SE 9 以来,终结器已被弃用。它们实现起来复杂,并且语义设计不良。例如,终结器可以通过将引用存储在静态字段中而使对象再次可达。因此,终结器不会被调用。我们建议您用弱引用和引用队列替换终结器。

线程 #

Native Image 不实现 java.lang.Thread 中早已弃用的方法,例如 Thread.stop()

不安全内存访问 #

如果类在构建时初始化,则使用 sun.misc.Unsafe 访问的字段需要为静态分析明确标记。在大多数情况下,这会自动发生:存储在 static final 字段中的字段偏移量会自动从托管值(native image 构建器运行的 Java VM 的字段偏移量)重写为原生可执行值,作为重写的一部分,该字段被标记为 Unsafe 访问。对于非标准模式,字段偏移量可以使用注解 RecomputeFieldValue 手动重新计算。

调试和监控 #

Java 有一些可选的规范,Java 实现可以用于调试和监控 Java 程序,包括 JVMTI。它们帮助您在运行时监控 Java VM 的事件,例如编译,这些事件在大多数原生镜像中不会发生。这些接口建立在 Java 字节码在运行时可用的假设之上,而对于使用封闭世界优化构建的原生镜像来说并非如此。由于 native-image 构建器生成原生可执行文件,用户必须使用原生调试器和监控工具(如 GDB 或 VTune),而不是针对 Java 的工具。JVMTI 和其他基于字节码的工具不支持 Native Image。

Linux AArch64 架构上的限制

大多数 Native Image 功能在 Linux AArch64 架构上都受支持,但以下限制除外。

  • -R:[+|-]WriteableCodeCache:必须禁用。
  • --libc=<value>:不支持 musl

在此处查找 native-image 构建器的选项列表:Options

联系我们