codecamp

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 
annotationProcessor("io.micronaut:micronaut-graal")
<annotationProcessorPaths>
    <path>
        <groupId>io.micronaut</groupId>
        <artifactId>micronaut-graal</artifactId>
    </path>
</annotationProcessorPaths>

该处理器生成额外的类来实现 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)
)
  1. 值成员指定哪些类需要反射。

  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)
  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。


Micronaut Kotlin 支持
Micronaut 创建 Endpoints
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

Micronaut 独立命令行应用程序

Micronaut 安全

Micronaut 多租户

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }