Micronaut GraalVM 支持
GraalVM 是来自 Oracle 的新型通用虚拟机,它支持多语言运行时环境以及将 Java 应用程序编译为本地机器代码的能力。
任何 Micronaut 应用程序都可以使用 GraalVM JVM 运行,但是已向 Micronaut 添加了特殊支持以支持使用 GraalVM 的本机图像工具运行 Micronaut 应用程序。
Micronaut 目前支持 GraalVM 版本 22.0.0.2,团队正在改进每个新版本的支持。但是,如果您发现任何问题,请不要犹豫,报告问题。
Micronaut 的许多模块和第三方库已经过验证可以与 GraalVM 一起工作:HTTP 服务器、HTTP 客户端、Function 支持、Micronaut Data JDBC 和 JPA、Service Discovery、RabbitMQ、Views、Security、Zipkin 等。对其他模块的支持是不断发展,并将随着时间的推移而改进。
入门
仅在 Java 或 Kotlin 项目中支持使用 GraalVM 的原生图像工具。 Groovy 严重依赖于 GraalVM 仅部分支持的反射。
要开始使用 GraalVM,请首先通过入门说明或使用 Sdkman! 安装 GraalVM SDK。
作为 GraalVM 原生镜像的微服务
开始使用 Micronaut 和 GraalVM
从 Micronaut 2.2 开始,任何 Micronaut 应用程序都可以使用 Micronaut Gradle 或 Maven 插件构建到原生镜像中。首先,创建一个新的应用程序:
创建 GraalVM 原生微服务
$ mn create-app hello-world
您可以使用 --build maven 进行 Maven 构建。
使用 Docker 构建原生镜像
要使用 Gradle 和 Docker 构建您的本机映像,请运行:
使用 Docker 和 Gradle 构建原生镜像
$ ./gradlew dockerBuildNative
要使用 Maven 和 Docker 构建您的本机映像,请运行:
使用 Docker 和 Maven 构建原生镜像
$ ./mvnw package -Dpackaging=docker-native
不使用 Docker 构建原生镜像
要在不使用 Docker 的情况下构建本机映像,请通过入门说明或使用 Sdkman 安装 GraalVM SDK!:
使用 SDKman 安装 GraalVM 22.0.0.2
$ sdk install java 22.0.0.2.r11-grl
$ sdk use java 22.0.0.2.r11-grl
本机图像工具是从基础 GraalVM 发行版中提取的,可作为插件使用。要安装它,请运行:
安装本机图像工具
$ gu install native-image
现在,您可以通过运行 nativeCompile 任务来使用 Gradle 构建原生镜像:
使用 Gradle 创建原生镜像
$ ./gradlew nativeCompile
本机映像将构建在 build/native/nativeCompile 目录中。
要使用 Maven 和 Micronaut Maven 插件创建原生图像,请使用原生图像打包格式:
使用 Maven 创建原生镜像
$ ./mvnw package -Dpackaging=native-image
在目标目录中构建本机图像。
然后,您可以从构建它的目录运行本机映像。
运行本机图像
$ ./hello-world
了解 Micronaut 和 GraalVM
Micronaut 本身不依赖于反射或动态类加载,因此它会自动与 GraalVM 本机一起工作,但是 Micronaut 使用的某些第三方库可能需要额外输入有关反射使用的信息。
Micronaut 包含一个注解处理器,它有助于生成由原生图像工具自动获取的反射配置:
Gradle | Maven |
|
|
该处理器生成额外的类来实现 GraalReflectionConfigurer 接口并以编程方式注册反射配置。
例如下面的类:
package example;
import io.micronaut.core.annotation.ReflectiveAccess;
@ReflectiveAccess
class Test {
...
}
上面的示例导致 example.Test 的公共方法、声明的字段和声明的构造函数被注册为反射访问。
如果您有更高级的要求并且只希望包含某些字段或方法,请在任何构造函数、字段或方法上使用注释以仅包含特定字段、构造函数或方法。
为反射访问添加额外的类
为了通知 Micronaut 要包含在生成的反射配置中的其他类,可以使用许多注释,包括:
- @ReflectiveAccess - 可以在特定类型、构造函数、方法或字段上声明的注释,以仅对带注释的元素启用反射访问。
- @TypeHint - 允许批量配置对一种或多种类型的反射访问的注释
- @ReflectionConfig - 直接模拟 GraalVM 反射配置 JSON 格式的可重复注解
@ReflectiveAccess 注释通常用于特定类型、构造函数、方法或字段,而后两者通常用于模块或应用程序类以包含反射所需的类。例如,以下内容来自带有@TypeHint 的 Micronaut 的 Jackson 模块:
使用@TypeHint 注解
@TypeHint(
value = { (1)
PropertyNamingStrategy.UpperCamelCaseStrategy.class,
ArrayList.class,
LinkedHashMap.class,
HashSet.class
},
accessType = TypeHint.AccessType.ALL_DECLARED_CONSTRUCTORS (2)
)
值成员指定哪些类需要反射。
accessType 成员指定是否只需要类加载访问权限,或者是否需要对所有公共成员进行完全反射。
或者使用可重复的 @ReflectionConfig 注释,并允许每种类型进行不同的配置:
使用@ReflectionConfig 注解
@ReflectionConfig(
type = PropertyNamingStrategy.UpperCamelCaseStrategy.class,
accessType = TypeHint.AccessType.ALL_DECLARED_CONSTRUCTORS
)
@ReflectionConfig(
type = ArrayList.class,
accessType = TypeHint.AccessType.ALL_DECLARED_CONSTRUCTORS
)
@ReflectionConfig(
type = LinkedHashMap.class,
accessType = TypeHint.AccessType.ALL_DECLARED_CONSTRUCTORS
)
@ReflectionConfig(
type = HashSet.class,
accessType = TypeHint.AccessType.ALL_DECLARED_CONSTRUCTORS
)
生成原生图像
GraalVM 的 native-image 命令生成本机图像。您可以手动使用此命令生成您的本机映像。例如:
native-image
命令
native-image --class-path build/libs/hello-world-0.1-all.jar (1)
- 类路径参数指的是 Micronaut 着色的 JAR
构建映像后,使用本机映像名称运行应用程序:
运行本机应用程序
$ ./hello-world
15:15:15.153 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 14ms. Server Running: http://localhost:8080
如您所见,本机图像启动在几毫秒内完成,并且内存消耗不包括 JVM 的开销(本机 Micronaut 应用程序仅使用 20mb 内存运行)。
资源文件生成
从 Micronaut 3.0 开始,自动生成 resource-config.json 文件现在是 Gradle 和 Maven 插件的一部分。
GraalVM 和 Micronaut 常见问题解答
Micronaut 如何在 GraalVM 上运行?
Micronaut 具有依赖注入和不使用反射的面向方面编程运行时。这使得 Micronaut 应用程序更容易在 GraalVM 上运行,因为存在兼容性问题,尤其是在原生图像中的反射方面。
如何让使用 picocli 的 Micronaut 应用程序在 GraalVM 上运行?
Picocli 提供了一个 picocli-codegen 模块,其中包含一个用于生成 GraalVM 反射配置文件的工具。该工具可以作为构建的一部分手动或自动运行。该模块的自述文件包含使用说明和代码片段,用于配置 Gradle 和 Maven 以在构建过程中自动生成 cli-reflect.json 文件。运行 native-image 工具时,将生成的文件添加到 -H:ReflectionConfigurationFiles 选项。
其他第三方库呢?
Micronaut 不能保证第三方库在 GraalVM SubstrateVM 上工作,这取决于每个单独的库来实现支持。
我得到一个“Class XXX is instantiated reflectively…”异常。我该怎么办?
如果您收到如下错误:
Class myclass.Foo[] is instantiated reflectively but was never registered. Register the class by using org.graalvm.nativeimage.RuntimeReflection
您可能需要手动调整生成的 reflect.json 文件。对于常规类,您需要在数组中添加一个条目:
[
{
"name" : "myclass.Foo",
"allDeclaredConstructors" : true
},
...
]
对于数组,这必须使用 Java JVM 内部数组表示。例如:
[
{
"name" : "[Lmyclass.Foo;",
"allDeclaredConstructors" : true
},
...
]
如果我想使用 -Xmx 设置堆的最大大小,但出现 OutOfMemoryError 怎么办?
如果您在用于构建本机映像的 Dockerfile 中设置最大堆大小,您可能会遇到如下运行时错误:
java.lang.OutOfMemoryError: Direct buffer memory
问题是 Netty 尝试使用 io.netty.allocator.pageSize 和 io.netty.allocator.maxOrder 的默认设置为每个块分配 16MB 的内存:
int defaultChunkSize = DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER; // 8192 << 11 = 16MB
最简单的解决方案是在 Dockerfile 的入口点中明确指定 io.netty.allocator.maxOrder。使用 -Xmx64m 的工作示例:
ENTRYPOINT ["/app/application", "-Xmx64m", "-Dio.netty.allocator.maxOrder=8"]
要更进一步,您还可以尝试使用 io.netty.allocator.numHeapArenas 或 io.netty.allocator.numDirectArenas。
更多建议: