运行演示应用程序

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 二进制文件,它将

  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 如何执行它。

观看将 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 的简短演示。


联系我们