运行演示应用程序

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 二进制文件,该脚本将执行以下操作:

  1. 将 Java 源代码构建为字节码
  2. 构建 JAR 文件
  3. 构建原生可执行文件

构建完成后,您可以观察生成的二进制文件(fileldd 是 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 上应用的简短演示。


联系我们