- 适用于 JDK 24 的 GraalVM(最新)
- 适用于 JDK 25 的 GraalVM(早期访问)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发构建
运行演示应用程序
Espresso 是 Java 虚拟机规范的实现,除了能够运行 Java 或其他 JVM 语言的应用程序外,它还提供了一些有趣的功能。例如,增强的 HotSwap 功能通过支持无限热代码重载来提高开发人员的生产力。此外,为了说明 Espresso 的功能,请看以下简短示例。
Java 中的 AOT 与 JIT 混合 #
GraalVM Native Image 技术允许将应用程序提前(AOT)编译为可执行的本地二进制文件,这些文件
- 独立运行
- 即时启动
- 内存占用更低
使用 Native Image 的主要权衡是,您的程序的分析和编译是在封闭世界假设下进行的,这意味着静态分析需要处理应用程序中将执行的所有字节码。这使得使用动态类加载或反射等一些语言功能变得复杂。
Espresso 是一个基于 Truffle 框架构建的 JVM 字节码解释器的 JVM 实现。它本质上是一个 Java 应用程序,就像 Truffle 框架本身和 GraalVM JIT 编译器一样。这三者都可以使用 native-image
进行提前编译。将 Espresso 用于应用程序的某些部分,可以隔离所需的动态行为,同时仍在其余代码上使用本地可执行文件。
以典型的 Java Shell 工具 (JShell) 为例,它是一个命令行应用程序。它是一个能够评估 Java 代码的 REPL,由两部分组成:
- 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 如何执行它。
观看 Espresso 演示中 AOT 和 JIT 混合编译代码的视频版本。
带 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 上应用的简短演示。