Introducing GraalVM Native Images

GraalVM 本机映像提供了一种部署和运行 Java 应用程序的新方法。与 Java 虚拟机相比,本机映像可以在更小的内存占用空间中运行,并且启动速度快得多。

GraalVM Native Images provide a new way to deploy and run Java applications. Compared to the Java Virtual Machine, native images can run with a smaller memory footprint and with much faster startup times.

它们非常适合使用容器映像部署的应用程序,尤其适合与 “功能即服务” (FaaS) 平台结合使用时。

They are well suited to applications that are deployed using container images and are especially interesting when combined with "Function as a service" (FaaS) platforms.

与为 JVM 编写的传统应用程序不同,GraalVM 本机映像应用程序需要预先处理才能创建可执行文件。这种预先处理涉及从其主要入口点静态分析应用程序代码。

Unlike traditional applications written for the JVM, GraalVM Native Image applications require ahead-of-time processing in order to create an executable. This ahead-of-time processing involves statically analyzing your application code from its main entry point.

GraalVM Native Image 是一款针对特定平台的完整可执行文件。您无需部署 Java Virtual Machine 来运行本机映像。

A GraalVM Native Image is a complete, platform-specific executable. You do not need to ship a Java Virtual Machine in order to run a native image.

如果您只是想开始使用并试验 GraalVM,您可以跳至 “Developing Your First GraalVM Native Application” 部分,之后再返回此部分。

If you just want to get started and experiment with GraalVM you can skip ahead to the “Developing Your First GraalVM Native Application” section and return to this section later.

Key Differences with JVM Deployments

GraalVM Native Image 是预先生成的,这意味着本机应用程序和基于 JVM 的应用程序之间存在一些关键差异。主要差异如下:

The fact that GraalVM Native Images are produced ahead-of-time means that there are some key differences between native and JVM based applications. The main differences are:

  • Static analysis of your application is performed at build-time from the main entry point.

  • Code that cannot be reached when the native image is created will be removed and won’t be part of the executable.

  • GraalVM is not directly aware of dynamic elements of your code and must be told about reflection, resources, serialization, and dynamic proxies.

  • The application classpath is fixed at build time and cannot change.

  • There is no lazy class loading, everything shipped in the executables will be loaded in memory on startup.

  • There are some limitations around some aspects of Java applications that are not fully supported.

基于这些差异,Spring 使用一个称为 Spring Ahead-of-Time processing 的进程,这会带来进一步的限制。请务必至少阅读下一部分的开头,以了解这些限制。

On top of those differences, Spring uses a process called Spring Ahead-of-Time processing, which imposes further limitations. Please make sure to read at least the beginning of the next section to learn about those.

GraalVM 参考文档中的 {url-graal-docs-native-image}/metadata/Compatibility/[本机映像兼容性指南] 部分提供了有关 GraalVM 限制的更多详细信息。

The {url-graal-docs-native-image}/metadata/Compatibility/[Native Image Compatibility Guide] section of the GraalVM reference documentation provides more details about GraalVM limitations.

Understanding Spring Ahead-of-Time Processing

典型的 Spring Boot 应用程序是相当动态的,配置在运行时执行。事实上,Spring Boot 自动配置的概念在很大程度上依赖于对运行时状态做出反应,以便正确配置各项工作。

Typical Spring Boot applications are quite dynamic and configuration is performed at runtime. In fact, the concept of Spring Boot auto-configuration depends heavily on reacting to the state of the runtime in order to configure things correctly.

尽管可以将这些应用程序的动态方面告知 GraalVM,但这样做会消除静态分析的大部分好处。因此,在使用 Spring Boot 创建本机映像时,会假定一个封闭的世界,而应用程序的动态方面受到限制。

Although it would be possible to tell GraalVM about these dynamic aspects of the application, doing so would undo most of the benefit of static analysis. So instead, when using Spring Boot to create native images, a closed-world is assumed and the dynamic aspects of the application are restricted.

封闭世界假设暗示了,除了 the limitations created by GraalVM itself 之外,以下限制:

A closed-world assumption implies, besides the limitations created by GraalVM itself, the following restrictions:

  • The beans defined in your application cannot change at runtime, meaning:

    • The Spring @Profile annotation and profile-specific configuration have limitations.

    • Properties that change if a bean is created are not supported (for example, @ConditionalOnProperty and .enable properties).

当这些限制到位时,Spring 可以在构建时执行预先处理,并生成 GraalVM 可以使用的其他资。经过 Spring AOT 处理的应用程序通常会生成:

When these restrictions are in place, it becomes possible for Spring to perform ahead-of-time processing during build-time and generate additional assets that GraalVM can use. A Spring AOT processed application will typically generate:

  • Java source code

  • Bytecode (for dynamic proxies etc)

  • GraalVM JSON hint files:

    • Resource hints (resource-config.json)

    • Reflection hints (reflect-config.json)

    • Serialization hints (serialization-config.json)

    • Java Proxy Hints (proxy-config.json)

    • JNI Hints (jni-config.json)

Source Code Generation

Spring 应用程序由 Spring Bean 组成。Spring Framework 在内部使用两个不同的概念来管理 bean。有 bean 实例,它们是已经创建的实际实例,并且可以注入到其他 bean 中。还有 bean 定义,用于定义 bean 的属性以及如何创建其实例。

Spring applications are composed of Spring Beans. Internally, Spring Framework uses two distinct concepts to manage beans. There are bean instances, which are the actual instances that have been created and can be injected into other beans. There are also bean definitions which are used to define attributes of a bean and how its instance should be created.

如果我们取一个典型的 @Configuration 类:

If we take a typical @Configuration class:

bean 定义由解析 @Configuration 类和查找 @Bean 方法而创建。在以上示例中,我们为名为 myBean 的单例 bean 定义一个 BeanDefinition。我们还为 MyConfiguration 类本身创建一个 BeanDefinition

The bean definition is created by parsing the @Configuration class and finding the @Bean methods. In the above example, we’re defining a BeanDefinition for a singleton bean named myBean. We’re also creating a BeanDefinition for the MyConfiguration class itself.

myBean 实例必需时,Spring 知道它必须调用 myBean() 方法并使用结果。在 JVM 上运行时,@Configuration 类解析会在您的应用程序启动时发生,而 @Bean 方法则会利用反射机制调用。

When the myBean instance is required, Spring knows that it must invoke the myBean() method and use the result. When running on the JVM, @Configuration class parsing happens when your application starts and @Bean methods are invoked using reflection.

在创建一个本机映像时,Spring 将以不同的方式运作。它不会在运行时解析 @Configuration 类并生成 bean 定义,而是在构建时进行此操作。在 bean 定义被发现后,它们会被处理并转换为可以被 GraalVM 编译器分析的源代码。

When creating a native image, Spring operates in a different way. Rather than parsing @Configuration classes and generating bean definitions at runtime, it does it at build-time. Once the bean definitions have been discovered, they are processed and converted into source code that can be analyzed by the GraalVM compiler.

Spring AOT 流程将以上配置类转换为类似这样的代码:

The Spring AOT process would convert the configuration class above to code like this:

根据 Bean 定义的性质,生成的 exact 代码可能会有所不同。

The exact code generated may differ depending on the nature of your bean definitions.

您可以看到,上面生成是代码为 @Configuration 类创建了等效的 Bean 定义,但以 GraalVM 可以理解的直接方式进行。

You can see above that the generated code creates equivalent bean definitions to the @Configuration class, but in a direct way that can be understood by GraalVM.

存在一个适用于 myConfiguration bean 的 Bean 定义,以及一个适用于 myBean 的 Bean 定义。当需要一个 myBean 实例时,将调用一个 BeanInstanceSupplier。此供应商将在 myConfiguration bean 上调用 myBean() 方法。

There is a bean definition for the myConfiguration bean, and one for myBean. When a myBean instance is required, a BeanInstanceSupplier is called. This supplier will invoke the myBean() method on the myConfiguration bean.

在 Spring AOT 处理期间,你的应用程序将启动到 Bean 定义可用的地步。Bean 实例不会在 AOT 处理阶段创建。

During Spring AOT processing your application is started up to the point that bean definitions are available. Bean instances are not created during the AOT processing phase.

Spring AOT 将为您的所有 Bean 定义生成类似这样的代码。当需要 Bean 后处理时,它还将生成代码(例如,调用 @Autowired 方法)。还将生成一个 ApplicationContextInitializer,Spring Boot 将使用它在 AOT 处理的应用程序实际上运行时初始化 ApplicationContext

Spring AOT will generate code like this for all your bean definitions. It will also generate code when bean post-processing is required (for example, to call @Autowired methods). An ApplicationContextInitializer will also be generated which will be used by Spring Boot to initialize the ApplicationContext when an AOT processed application is actually run.

虽然 AOT 生成的源代码冗长,但它非常易读,并且在调试应用程序时很有帮助。使用 Maven 时,可以在 target/spring-aot/main/sources 中找到生成的源文件,使用 Gradle 时可以在 build/generated/aotSources 中找到生成的源文件。

Although AOT generated source code can be verbose, it is quite readable and can be helpful when debugging an application. Generated source files can be found in target/spring-aot/main/sources when using Maven and build/generated/aotSources with Gradle.

Hint File Generation

除了生成源文件外,Spring AOT 引擎还将生成 GraalVM 使用的提示文件。提示文件包含 JSON 数据,描述了 GraalVM 如何处理它不能通过直接检查代码理解的事物。

In addition to generating source files, the Spring AOT engine will also generate hint files that are used by GraalVM. Hint files contain JSON data that describes how GraalVM should deal with things that it can’t understand by directly inspecting the code.

例如,您可能在私有方法中使用 Spring 注解。Spring 将需要使用反射来调用私有方法,即使是在 GraalVM 上也是如此。当出现这种情况时,Spring 可以编写一个反射提示,以便 GraalVM 知道即使没有直接调用私有方法,它仍然需要在本地映像中可用。

For example, you might be using a Spring annotation on a private method. Spring will need to use reflection in order to invoke private methods, even on GraalVM. When such situations arise, Spring can write a reflection hint so that GraalVM knows that even though the private method isn’t called directly, it still needs to be available in the native image.

提示文件在 META-INF/native-image 下生成,GraalVM 会在那里自动选取它们。

Hint files are generated under META-INF/native-image where they are automatically picked up by GraalVM.

使用 Maven 时,可以在 target/spring-aot/main/resources 中找到生成的提示文件,使用 Gradle 时可以在 build/generated/aotResources 中找到生成的提示文件。

Generated hint files can be found in target/spring-aot/main/resources when using Maven and build/generated/aotResources with Gradle.

Proxy Class Generation

Spring 有时需要生成代理类以使用其他功能来增强您编写的代码。为此,它使用了直接生成字节码的 cglib 库。

Spring sometimes needs to generate proxy classes to enhance the code you’ve written with additional features. To do this, it uses the cglib library which directly generates bytecode.

当应用程序在 JVM 上运行时,代理类会在应用程序运行时动态生成。在创建一个本地映像时,这些代理需要在构建时创建,以便它们可以被 GraalVM 包含。

When an application is running on the JVM, proxy classes are generated dynamically as the application runs. When creating a native image, these proxies need to be created at build-time so that they can be included by GraalVM.

与源码生成不同,在调试程序时, 生成的字节码并不是特别有帮助。但是,如果你需要使用诸如 javap 之类的工具检查 .class 文件的内容,则可以在 Maven 中找到 target/spring-aot/main/classes 和在 Gradle 中找到 build/generated/aotClasses

Unlike source code generation, generated bytecode isn’t particularly helpful when debugging an application. However, if you need to inspect the contents of the .class files using a tool such as javap you can find them in target/spring-aot/main/classes for Maven and build/generated/aotClasses for Gradle.