- 适用于 JDK 23 的 GraalVM(最新)
- 适用于 JDK 24 的 GraalVM(抢先体验版)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发版本
运行演示应用程序
Espresso 是 Java 虚拟机规范的实现,除了能够运行 Java 或其他 JVM 语言中的应用程序外,它还提供了一些有趣的功能。例如,增强的 HotSwap 功能 通过支持无限热代码重新加载来提高开发人员的生产力。此外,为了说明 Espresso 的功能,请考虑以下简短示例。
将 AOT 和 JIT 混合用于 Java #
GraalVM Native Image 技术允许在运行前(AOT)将应用程序编译为可执行的本机二进制文件,这些文件
- 是独立的
- 立即启动
- 具有较低的内存使用率
使用 Native Image 的主要权衡是,程序的分析和编译是在封闭世界假设下进行的,这意味着静态分析需要处理应用程序中将要执行的所有字节码。这使得使用某些语言特性(如动态类加载或反射)变得很棘手。
Espresso 是 JVM 字节码解释器的 JVM 实现,它构建在 Truffle 框架 之上。它本质上是一个 Java 应用程序,与 Truffle 框架本身和 GraalVM JIT 编译器一样。所有这三者都可以使用 native-image
提前编译。
以典型的 Java Shell 工具(JShell)为例,它是一个命令行应用程序。它是一个 REPL,能够评估 Java 代码,并且由两个部分组成
- UI - 处理输入输出的 CLI 应用程序
- 用于运行您输入到 Shell 中的代码的后端处理器。
这种设计自然地符合我们试图说明的要点。我们可以构建 JShell 的 UI 部分的本机可执行文件,并使其包含 Espresso 来动态运行在运行时指定的代码。
先决条件
克隆包含演示应用程序的 项目 并导航到 espresso-jshell
目录
git clone https://github.com/graalvm/graalvm-demos.git
cd graalvm-demos/espresso-jshell
JShell 实现实际上是正常的 JShell 启动器代码,它只接受 Espresso 对执行引擎的实现。
将 AOT 编译的部分与动态评估代码的组件绑定在一起的“粘合”代码位于 EspressoExecutionControl
类中。它在 Espresso 上下文中加载 JShell 类并将输入委托给它们
protected final Lazy<Value> ClassBytecodes = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$ClassBytecodes"));
protected final Lazy<Value> byte_array = Lazy.of(() -> loadClass("[B"));
protected final Lazy<Value> ExecutionControlException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$ExecutionControlException"));
protected final Lazy<Value> RunException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$RunException"));
protected final Lazy<Value> ClassInstallException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$ClassInstallException"));
protected final Lazy<Value> NotImplementedException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$NotImplementedException"));
protected final Lazy<Value> EngineTerminationException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$EngineTerminationException"));
protected final Lazy<Value> InternalException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$InternalException"));
protected final Lazy<Value> ResolutionException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$ResolutionException"));
protected final Lazy<Value> StoppedException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$StoppedException"));
protected final Lazy<Value> UserException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$UserException"));
还有更多代码来正确传递值并转换异常。要试用它,请使用提供的脚本构建 espresso-jshell
二进制文件,它将
- 将 Java 源代码构建为字节码
- 构建 JAR 文件
- 构建本机可执行文件
构建完成后,您可以观察到生成的二进制文件(file
和 ldd
是 Linux 命令)
file ./espresso-jshell
ldd ./espresso-jshell
它确实是一个不依赖 JVM 的二进制文件,您可以运行它并注意到它启动速度有多快
./espresso-jshell
| Welcome to JShell -- Version 11.0.10
| For an introduction type: /help intro
jshell> 1 + 1
1 ==> 2
尝试将新代码加载到 JShell 中并查看 Espresso 如何执行它。
观看将 AOT 和 JIT 编译的代码与 Espresso 演示混合的视频版本。
带有 Espresso 的 GraalVM 工具 #
Espresso 是 GraalVM 生态系统中的一个适当部分,与其他 GraalVM 支持的语言一样,它默认情况下也支持开发人员工具。 Truffle 框架 集成到诸如调试器、探查器、内存分析器、Instrumentation API 等工具中。语言解释器需要使用一些注释标记 AST 节点以支持这些工具。
例如,要使用探查器,语言解释器需要标记根节点。为了调试目的,语言表达式应被标记为可检测的,应指定变量的范围,等等。语言解释器不需要自己与工具集成。因此,您可以使用 CPU 采样器或内存跟踪器工具开箱即用地探查 Espresso 上的 Java 应用程序。
例如,如果我们有一个像下面这样的类来计算素数
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.LongStream;
public class Main {
public static void main(String[] args) {
Main m = new Main();
for (int i = 0; i < 100_000; i++) {
System.out.println(m.random(100));
}
}
private Random r = new Random(41);
public List<Long> random(int upperbound) {
int to = 2 + r.nextInt(upperbound - 2);
int from = 1 + r.nextInt(to - 1);
return primeSequence(from, to);
}
public static List<Long> primeSequence(long min, long max) {
return LongStream.range(min, max)
.filter(Main::isPrime)
.boxed()
.collect(Collectors.toList());
}
public static boolean isPrime(long n) {
return LongStream.rangeClosed(2, (long) Math.sqrt(n))
.allMatch(i -> n % i != 0);
}
}
构建此程序,并使用 --cpusampler
选项运行它。
javac Main.java
java -truffle --cpusampler Main > output.txt
在 output.txt
文件的末尾,您将找到探查器输出、方法的直方图以及执行花费了多少时间。您也可以尝试使用 --memtracer
选项进行实验,以查看此程序中的分配发生在何处。
java -truffle --experimental-options --memtracer Main > output.txt
GraalVM 提供的其他工具包括 Chrome 调试器、代码覆盖率 和 GraalVM Insight。
对开发人员工具的“开箱即用”支持使 Espresso 成为 JVM 的一种有趣选择。
观看 GraalVM 内置工具用于 Espresso 的简短演示。