GraalVM 原生镜像简介
GraalVM 原生镜像为 Java 应用提供了一种全新的部署与运行方式。 与 Java 虚拟机相比,原生镜像可实现更小的内存占用和更快的启动速度。
它们非常适合通过容器镜像部署的应用,尤其在结合“函数即服务”(FaaS)平台时更具吸引力。
与传统 JVM 应用不同,GraalVM 原生镜像应用需要提前进行处理(AOT),以生成可执行文件。 该处理过程会从主入口点对应用代码进行静态分析。
GraalVM 原生镜像是完整的、平台相关的可执行文件。 无需携带 Java 虚拟机即可运行原生镜像。
如果你只想快速体验 GraalVM,可以直接跳转到 开发你的第一个 GraalVM Native 应用程序,稍后再回到本节。 |
与 JVM 部署的关键区别
GraalVM 原生镜像采用提前处理,因此与基于 JVM 的应用存在一些关键差异。 主要区别包括:
-
应用的静态分析在构建时从
main
入口点进行。 -
创建原生镜像时无法到达的代码会被移除,不会包含在可执行文件中。
-
GraalVM 无法直接感知代码中的动态元素,需显式告知反射、资源、序列化和动态代理相关信息。
-
应用类路径在构建时固定,无法变更。
-
启动时会加载所有可执行文件中的内容,不支持延迟类加载。
-
某些 Java 应用特性存在部分不支持的限制。
此外,Spring 采用了 Spring 提前处理(AOT),带来更多限制。 请务必阅读下一节开头内容以了解这些限制。
原生镜像兼容性指南 提供了更多关于 GraalVM 限制的详细信息。 |
理解 Spring 提前处理(AOT)
典型的 Spring Boot 应用非常动态,配置在运行时完成。 实际上,Spring Boot 自动配置机制高度依赖于运行时状态以正确配置各项内容。
虽然可以让 GraalVM 感知应用的动态特性,但这样会削弱静态分析带来的优势。 因此,使用 Spring Boot 构建原生镜像时,假定应用为“封闭世界”,并限制其动态特性。
“封闭世界”假设除了 GraalVM 本身的限制 外,还包括以下约束:
-
应用中定义的 bean 运行时不可变,意味着:
-
依赖 bean 是否创建而变化的属性不被支持(如
@ConditionalOnProperty
及.enabled
属性)。
在这些限制下,Spring 可以在构建时进行提前处理,生成 GraalVM 可用的额外资源。 经过 Spring AOT 处理的应用通常会生成:
-
Java 源代码
-
字节码(如动态代理等)
-
GraalVM JSON 提示文件,位于
META-INF/native-image/{groupId}/{artifactId}/
:-
资源提示(
resource-config.json
) -
反射提示(
reflect-config.json
) -
序列化提示(
serialization-config.json
) -
Java 代理提示(
proxy-config.json
) -
JNI 提示(
jni-config.json
)
-
如自动生成的提示不足,你也可以 自定义提示。
源代码生成
Spring 应用由 Spring Bean 组成。 Spring Framework 内部通过两种方式管理 bean: 一是 bean 实例,即已创建并可注入到其他 bean 的对象; 二是 bean 定义,用于描述 bean 的属性及其实例化方式。
以典型的 @Configuration
类为例:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyConfiguration {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
bean 定义通过解析 @Configuration
类及其 @Bean
方法生成。
上述示例中,我们为名为 myBean
的单例 bean 创建了 BeanDefinition
,同时也为 MyConfiguration
类本身创建了 bean 定义。
当需要 myBean
实例时,Spring 会调用 myBean()
方法并使用其返回值。
在 JVM 上运行时,@Configuration
类的解析发生在应用启动时,@Bean
方法通过反射调用。
创建原生镜像时,Spring 的处理方式不同。
它会在构建时解析 @Configuration
类并生成 bean 定义。
一旦发现所有 bean 定义,便将其处理并转换为 GraalVM 编译器可分析的源代码。
Spring AOT 过程会将上述配置类转换为如下代码:
import org.springframework.beans.factory.aot.BeanInstanceSupplier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
/**
* Bean definitions for {@link MyConfiguration}.
*/
public class MyConfiguration__BeanDefinitions {
/**
* Get the bean definition for 'myConfiguration'.
*/
public static BeanDefinition getMyConfigurationBeanDefinition() {
Class<?> beanType = MyConfiguration.class;
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
beanDefinition.setInstanceSupplier(MyConfiguration::new);
return beanDefinition;
}
/**
* Get the bean instance supplier for 'myBean'.
*/
private static BeanInstanceSupplier<MyBean> getMyBeanInstanceSupplier() {
return BeanInstanceSupplier.<MyBean>forFactoryMethod(MyConfiguration.class, "myBean")
.withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(MyConfiguration.class).myBean());
}
/**
* Get the bean definition for 'myBean'.
*/
public static BeanDefinition getMyBeanBeanDefinition() {
Class<?> beanType = MyBean.class;
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
beanDefinition.setInstanceSupplier(getMyBeanInstanceSupplier());
return beanDefinition;
}
}
实际生成的代码可能因 bean 定义的不同而有所差异。 |
如上所示,生成的代码以直接方式创建了等价的 bean 定义,便于 GraalVM 理解。
myConfiguration
和 myBean
都有对应的 bean 定义。
当需要 myBean
实例时,会调用 BeanInstanceSupplier
,该 supplier 会在 myConfiguration
bean 上调用 myBean()
方法。
Spring AOT 处理期间,应用会启动到 bean 定义可用的阶段,但不会创建 bean 实例。 |
Spring AOT 会为所有 bean 定义生成类似代码。
如需 bean 后处理(如调用 @Autowired
方法),也会生成相应代码。
还会生成 ApplicationContextInitializer
,用于在实际运行 AOT 处理应用时由 Spring Boot 初始化 ApplicationContext
。
虽然 AOT 生成的源代码较为冗长,但可读性较好,有助于调试应用。
Maven 下生成的源文件位于 target/spring-aot/main/sources ,Gradle 下为 build/generated/aotSources 。
|
提示文件生成
除了生成源文件,Spring AOT 引擎还会生成 GraalVM 使用的提示文件。 提示文件为 JSON 格式,描述 GraalVM 如何处理无法直接通过代码分析识别的内容。
例如,你可能在私有方法上使用了 Spring 注解。 即使在 GraalVM 上,Spring 也需要通过反射调用私有方法。 遇到此类情况时,Spring 会写入反射提示,让 GraalVM 知道即使该私有方法未被直接调用,也需在原生镜像中保留。
提示文件生成在 META-INF/native-image
下,GraalVM 会自动读取。
Maven 下生成的提示文件位于 target/spring-aot/main/resources ,Gradle 下为 build/generated/aotResources 。
|