注:
- Oracle 提供的免费实验室环境中提供了本教程。
- 它使用 Oracle Cloud Infrastructure 身份证明、租户和区间示例值。完成实验室后,请使用特定于您的云环境的这些值替换这些值。
了解反射和 GraalVM Native Image
简介
此实验室面向开发人员,希望了解有关 GraalVM Native Image 中反射功能的更多信息。
GraalVM Native Image 允许提前将 Java 应用编译为自包含的原生可执行文件。使用 GraalVM Native Image 时,应用程序在运行时所需的代码将添加到本机可执行文件中。
这些原生可执行文件具有许多重要的优势,因为它们:
- 使用 JVM 所需的一小部分资源,因此运行成本更低
- 开始时间(以毫秒为单位)
- 立即提供高峰性能,无需热身
- 可以打包到轻量级容器映像,从而提高部署速度和效率
- 降低攻击面(在将来的实验室中会更详细)
许多先进的微服务框架都支持使用 GraalVM Native Image 进行提前编译,包括 Micronaut、Spring、Helidon 和 Quarkus。
此外,还有适用于 Native Image 的 Maven 和 Gradle 插件,可轻松构建、测试和运行 Java 应用,并使其成为原生可执行文件。
注意:Oracle Cloud Infrastructure (OCI) 无需额外付费即可提供 GraalVM 企业版。
估计的实验室时间:30 分钟
实验室目标
在此实验室中,您可以执行以下任务:
- 了解如何使用
native-image
构建工具构建在独立可执行文件中使用反射的 Java 代码 - 了解 GraalVM 提供的辅助配置工具
注意:每当您看到笔记本电脑图标时,您都需要在某个位置执行操作。注意。
# This is where we you will need to do something
您的开发环境由远程主机提供:具有 Oracle Linux 8、1 CPU 和 32GB 内存的 OCI 计算实例。
在远程主机准备就绪之前将显示 Luna Labs 桌面环境,这至多需要两分钟。
STEP 1:连接到虚拟主机并检查开发环境
通过在 Luna Desktop 环境中运行设置脚本,可以连接到远程主机。可通过“资源”选项卡使用此脚本
-
在桌面中,双击 Luna-Lab.html 图标。此时将打开该页面,其中显示特定于您的实验室的 Oracle Cloud Infrastructure 身份证明和信息。
-
将显示资源选项卡。请注意,资源标题旁边的齿轮在计算实例预配到茶云时会旋转。
-
预配实例时,这至多需要 2 分钟,您将在资源选项卡上看到以下内容
-
从“资源”选项卡复制设置 VS 代码环境的配置脚本。单击查看详细信息链接可显示配置。复制此项,如下面的屏幕截图中所示。
-
打开终端,如下面的屏幕截图中所示:
-
将配置代码粘贴到终端中,终端将打开 VS 代码。
结束了!祝贺您,您现在已成功连接到 Oracle Cloud 中的远程主机!
STEP 2:关闭的世界假设
使用 GraalVM 提供的 native-image
工具构建独立的可执行文件与构建 Java 应用有一点不同。本机映像使用称为封闭世界假设的内容。
"Close World" 假设意味着在构建时(即 native-image
工具正在构建独立可执行文件时)必须知道(观察和分析)应用程序中可以调用的所有字节码。
在继续之前,不妨先了解一下使用 GraalVM Native Image 构建的应用的构建 / 运行模型。
- 将 Java 源代码编译为 Java 字节代码类
- 使用
native-image
工具将这些 Java 字节代码类构建到本地可执行文件中 - 运行本机可执行文件
但是,在步骤 2 中究竟会发生什么?
首先,native-image
工具执行分析以查看应用程序中的哪些类可访问。稍后我们将详细介绍。
其次,已找到的类(已知可安全初始化)(Automatic Initialization of Safe Classes) 已初始化。已初始化类的类数据将加载到图像堆中,然后又保存到独立可执行文件中(在文本部分中)。这是 GraalVM native-image
工具的功能之一,可用于快速启动应用。
注意:这与对象初始化不同。对象初始化在本地可执行文件的运行时进行。
我们说,我们将回到可到达性专题。如前所述,分析确定哪些类、方法和字段需要包括在独立可执行文件中。分析是静态的,它不运行代码。分析可以确定某些动态类加载和反射使用的案例(请参见),但是在有些情况下,它无法获取。
为了处理 Java 的动态功能,需要告知分析使用了反射的类或动态加载的类。
让我们看看示例。
STEP 3:使用反射的示例
假设您具有以下类 ReflectionExample.java
(该副本位于目录 demo/ReflectionExample.java
中):
import java.lang.reflect.Method;
class StringReverser {
static String reverse(String input) {
return new StringBuilder(input).reverse().toString();
}
}
class StringCapitalizer {
static String capitalize(String input) {
return input.toUpperCase();
}
}
public class ReflectionExample {
public static void main(String[] args) throws ReflectiveOperationException {
String className = args[0];
String methodName = args[1];
String input = args[2];
Class<?> clazz = Class.forName(className);
Method method = clazz.getDeclaredMethod(methodName, String.class);
Object result = method.invoke(null, input);
System.out.println(result);
}
}
首先,在 VS 代码中创建终端。这是从 Terminal(终端)> New Terminal(新建终端)完成的。
接下来,让我们构建代码。在 shell 中,从 VS 代码中运行以下命令:
javac ReflectionExample.java
类 ReflectionExample
中的 main 方法可加载其名称作为参数传入的类,一个非常动态的用例!类的第二个参数是动态加载的类中应调用的方法名称。
让我们来运行一下,看看它的作用是什么。
java ReflectionExample StringReverser reverse "hello"
正如我们所期望的那样,通过反思找到了类 StringReverser
上的方法 reverse
。该方法被调用,反转了输入字符串“hello”。到目前为止还不错
好的,但是如果尝试在程序之外构建一个本机映像,会发生什么情况?我们来试一试。在 shell 中运行以下命令:
native-image --no-fallback ReflectionExample
注意:如果
native-image
的--no-fallback
选项无法构建独立的本机执行文件,则会导致生成失败。
现在,让我们运行生成的本机可执行文件并查看它的作用:
./reflectionexample StringReverser reverse "hello"
Exception in thread "main" java.lang.ClassNotFoundException: StringReverser
at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60)
at java.lang.Class.forName(DynamicHub.java:1214)
at ReflectionExample.main(ReflectionExample.java:21)
这里发生了什么情况?我们的本地可执行文件似乎找不到类 StringReverser
。这是怎么发生的?到现在,我想我们也许知道为什么封闭世界假设。
在执行 native-image
工具的分析期间,无法确定曾经使用过类 StringReverser
。因此,它将类从生成的本机可执行文件中删除。注:通过从独立可执行文件中删除不需要的类,该工具仅通过包括已知要使用的类来收缩构建的代码。正如我们刚才所看到的,这会引起人们的反思,但幸运的是,有办法处理。
STEP 4:引入本地映像反射配置
可以通过特殊配置文件告知 native-image
构建工具有关反射实例。这些文件在 JSON
中写入,可以通过使用标志传递到 native-image
工具。
因此,我们可以将哪些其他类型的配置信息传递到 native-image
构建工具?该工具当前支持读取包含以下各项详细信息的文件:
- 评估
- 资源 - 应用程序所需的资源文件
- Jni
- 动态代理
- 序列化
我们只研究如何在此实验室中进行反思,因此我们会重点关注这一点。
以下是这些文件的外观示例(从此处获取):
[
{
"name" : "java.lang.Class",
"queryAllDeclaredConstructors" : true,
"queryAllPublicConstructors" : true,
"queryAllDeclaredMethods" : true,
"queryAllPublicMethods" : true,
"allDeclaredClasses" : true,
"allPublicClasses" : true
},
{
"name" : "java.lang.String",
"fields" : [
{ "name" : "value" },
{ "name" : "hash" }
],
"methods" : [
{ "name" : "<init>", "parameterTypes" : [] },
{ "name" : "<init>", "parameterTypes" : ["char[]"] },
{ "name" : "charAt" },
{ "name" : "format", "parameterTypes" : ["java.lang.String", "java.lang.Object[]"] }
]
},
{
"name" : "java.lang.String$CaseInsensitiveComparator",
"queriedMethods" : [
{ "name" : "compare" }
]
}
]
由此可见,需要配置通过反射 API 访问的类和方法。我们可以手动执行此操作,但生成这些配置文件的最方便的方法是使用辅助配置 javaagent
。
STEP 5:本机映像,辅助配置:输入 Java 代理
可以从头开始编写完整的反射配置文件,但是 GraalVM Java 运行时会提供一个 java 跟踪代理 (javaagent
),该代理将在您运行应用程序时自动为您生成该文件。
我们来试一试。
在启用了跟踪代理的情况下运行应用程序。在 shell 中,运行以下命令:
# Note: the tracing agent parameter must come before the classpath and jar params on the command ine
java -agentlib:native-image-agent=config-output-dir=META-INF/native-image ReflectionExample StringReverser reverse "hello"
让我们看看创建的配置:
cat META-INF/native-image/reflect-config.json
[
{
"name":"StringReverser",
"methods":[{"name":"reverse","parameterTypes":["java.lang.String"] }]
}
]
您可以多次运行此进程,如果指定了 native-image-agent=config-merge-dir
,则会合并运行,如下例所示:
java -agentlib:native-image-agent=config-merge-dir=META-INF/native-image ReflectionExample StringCapitalizer capitalize "hello"
构建独立可执行文件现在将利用提供的配置。我们来构建:
native-image --no-fallback ReflectionExample
让我们看看效果是否更好:
./reflectionexample StringReverser reverse "hello"
确实!
结论
使用 GraalVM Native Image 构建独立可执行文件依赖于已关闭的世界假设,即我们需要在构建独立可执行文件时提前知道代码中可能出现的任何反射案例。
GraalVM 平台提供了一种在使用反射时指定 native-image
构建工具的方法。注:对于某些简单情况,native-image
工具可以自行发现这些内容。
GraalVM 平台还提供了一种通过 Java 跟踪代理搜索反射和其他动态行为的方法,并且可以自动生成 native-image
工具所需的配置文件。
使用跟踪代理时,应注意以下几点:
- 使用测试套件。您需要尽可能多地在代码中练习路径
- 您可能需要复查和编辑配置文件
我们希望您已学习过本教程,并了解在使用 Native Image 时如何处理反思。
了解更多
- 观看本机映像架构师 Christian Wimmer 的演示 GraalVM Native Image:针对 Java 的大规模静态分析
- GraalVM EE Native Image 参考文档
- 原生映像中的反射使用
- 本机图像中的类初始化
- 使用跟踪代理的辅助配置
更多学习资源
在 docs.oracle.com/learn 上浏览其他实验室,或者在 Oracle Learning YouTube 渠道上访问更多免费学习内容。此外,访问 education.oracle.com/learning-explorer 以成为 Oracle Learning Explorer。
有关产品文档,请访问 Oracle 帮助中心。
Understanding Reflection and GraalVM Native Image
F54908-01
March 2022
Copyright © 2022, Oracle and/or its affiliates.