Java神话与WebAssembly


当许多 Java 开发人员听到 WebAssembly 这个词时,他们首先想到的是“浏览器技术”。第二件事:“又是 JVM”。
毕竟,对于 Java 开发人员来说,浏览器中的应用程序是史前的。

在过去的几周里,围绕 WebAssembly 发布了很多公告,例如Docker+Wasm Technical Preview。作为一名 Java 极客,我认为我们不应该将这项技术视为一种时尚而忽视它。

WebAssembly是“Web 的字节码”,Java 和Wasm之间的相似之处。

神话 #1:JVM 是一个多语言编译目标
当然,每个人都知道 JVM 是最丰富、可互操作的语言生态系统之一。我们不仅有 Java,还有 Scala、Jython、JRuby、Clojure、Groovy、Kotlin 和许多其他语言。

然而,可悲的现实是,Java 字节码从未真正成为一个通用的编译目标。

“字节码遇见组合器:invokedynamic  在 JVM 上”,John Rose

Java 虚拟机 (JVM) 之所以被广泛采用,部分原因在于它的类文件格式,这种格式具有可移植性、紧凑性、模块化、可验证性,并且相当容易使用。然而,它只为一种语言——Java——设计,因此当用它来表达其他源语言的程序时,往往会出现阻碍开发和执行的“痛点”。

本文描述了JVM中引入invokedynamic操作码的方式和原因;事实上,它是专门为了支持以JVM为运行时的动态语言而引入的。在当时,这些语言有很多。这个操作码的加入并不是因为JVM应该支持这些语言;而是因为人们无论如何都会在这样做:所以,最好还是顺势而为承认它吧。

换句话说,当时的JVM并不是动态语言的一个恰当的编译目标。我们甚至可以说,JVM之所以成为编译器目标,并不是因为它是最好的编译目标,而是因为人们想与它互通有无,因为它已经被大量采用和支持......就像Javascript一样!

GRAALVM:一台虚拟机统领一切?
GraalVM项目最近已经成为主流。这个项目包括一个针对常规Java字节码的即时编译器,一个用于构建高效语言解释器的API,以及最近的一个本地镜像编译器。

GraalVM的最初目标之一是成为 "一个统治多语言的虚拟机",即成为一个多语言运行时。

但是Truffle并没有定义一个多语言编译目标。相反,Truffle API允许你为动态编程语言建立一个高效的JIT解释器,使用一个非常高级的表示法(一个基于AST的解释器)。

给吹毛求疵的人的注意。现在,一旦你进入编程语言的兔子洞,一切都变得有点 "玄"。事实上,通过Truffle,你可以为其他一些 "适当的 "字节码格式写一个JIT解释器。

事实上,已经有了一个基于Truffle的解释器用于LLVM(Sulong);当然,LLVM比特码bitcode 是为了成为一个多平台/多目标的编译目标。因此,根据反证法,你可以认为GraalVM/Truffle确实支持多平台的编译目标。

这在技术上是正确的(这是最好的一种正确),但有许多考虑因素,这里没有足够的空间来讨论它们:
简而言之,LLVM的比特码是为了成为一个编译目标,但它不一定是为了成为一个跨平台的运行语言(例如,根据你想针对的CPU/OS,你可能要使用的指令有轻微的变化)。
此外,相对于WebAssembly,它是一个多厂商的标准,GraalVM和Truffle至今仍是开源的、社区驱动的,但也是单一的实施工作(最近已经开始工作,将其引入OpenJDK,并可能引入Java语言规范)。

最终,WebAssembly也是GraalVM/Truffle能够支持的另一种语言,所以如果你想使用GraalVM,你甚至可以把定位到Wasm!


神话2:它只是另一种基于堆栈的语言虚拟机
WebAssembly被定义为结构化堆栈式虚拟机的虚拟指令集架构(ISA)。

这里的”结构化“这个词很关键,因为它与JVM的工作方式有很大的不同,比如说,JVM在实践中,在结构化堆栈机器中,大多数计算都使用堆栈的值,但控制流是用结构化的结构,如块、ifs和循环来表达。
此外,在WebAssembly语言中,一些指令既可以表示为 "简单 "也可以表示为 "嵌套"。

...详细点击标题

更多差异:内存管理
无论好坏,WebAssembly虚拟机与JVM大不相同的另一个领域是内存管理。

你可能知道,Java语言不要求你分配和删除内存,也不关心堆栈与堆的分配。
这不是一个语言层面的特性,这实际上也是虚拟机的工作方式。你没有在虚拟机级别处理内存的原语;事实上,堆分配的原语是可用的,但它们是作为JDK API暴露的。你没有办法选择不使用管理内存:你不能说 "我不关心垃圾收集的堆,我要做我自己的内存管理"。

在这个时候,WebAssembly正好相反。今天,大多数以WebAssembly为目标的语言真正管理自己的内存,这并不是巧合。有些语言做垃圾收集;但在这些情况下,他们必须推出自己的垃圾收集程序,因为虚拟机不提供这样的设施。

相反,在WebAssembly中,你可以得到一片线性内存,然后你可以用它做任何你想做的事情。分配,取消分配;如果你愿意,甚至可以移动它。虽然在某种程度上,这比JVM提供的功能更强大,但它也有一些注意事项。

例如,JVM不要求你指定一个对象的内存布局,因为它是由虚拟机来处理结构打包、字对齐等问题。在WebAssembly的情况下,你要处理这些问题。

一方面,这使WebAssembly成为人工管理的编程语言的完美目标,在这种情况下,人们期待并希望有更高程度的控制。另一方面,它可能会使这些语言更难相互操作。

现在,结构和对象布局是ABI关注的问题:对于 JVM 开发人员来说,这已成为过去,除了一些非常有限和值得注意的例外
有趣的是,WebAssembly 的 GC 规范草案最近取得了进展,它不仅处理垃圾收集,而且有效地描述了如何处理结构,以及如何使它们互操作,而不管原始语言是什么。因此,虽然这还没有准备好,但事情在不断发展,并且正在解决多个问题。

不仅仅是Web
即使您不关心前端,也不应将 WebAssembly 视为一种纯粹的前端技术。WebAssembly 的设计和规范中没有任何内容使其专门与前端相关联。

事实上,大多数主流 JavaScript 运行时现在都能够加载和链接 WebAssembly 二进制文件,甚至在浏览器之外也是如此;因此您可以在 Node.js 运行时运行 Wasm 可执行文件,使用一层薄薄的 JS 胶水代码与平台的其余部分进行交互。

还有许多纯 WebAssembly 运行时,例如WasmtimeWasmEdgewasmCloudWazero,它们完全脱离了 JavaScript 宿主。这些运行时通常比成熟的 JavaScript 引擎更轻量级,而且它们通常很容易嵌入到更大的项目中。

事实上,许多项目开始将 WebAssembly 作为一个多语言平台来托管扩展和插件。

例如,一个值得注意的例子是Envoy代理:代码库主要是C++;它确实支持插件,但有与浏览器插件相同的注意事项:你必须编译它们,你必须运送它们,它们可能不会以正确的权限级别运行,它们甚至可能在出现致命故障时拆毁整个进程。现在,你可以嵌入一个Lua或JS解释器,让你的用户以脚本方式获得成功:解释器更安全,因为它与你的主要业务逻辑隔离,而且它只以安全的方式与主机环境互动;主要的缺点是:你必须为你的用户选择一种语言。

或者,你可以直接嵌入一个WebAssembly运行时,让你的用户选择他们自己的语言,然后直接编译到Wasm。你会有同样的安全保证,而且用户会更高兴。

这些纯WebAssembly运行时并不只是用于扩展。许多项目正在创建Wasm原生API的薄层,以提供独立的平台。

例如,Fastly开发了一个用于边缘无服务器计算的平台,其中无服务器功能由用户提供的WebAssembly可执行文件实现。
Fermyon是一家初创公司,正在开发一个丰富的工具和基于Web的API的生态系统,以编写只使用Wasm的Web应用程序。他们的最新公告之一是他们的Fermyon云产品。

这些解决方案为特定的用例提供了定制的、临时的API;而这确实是使用WebAssembly的一种方式。但这并不是终点。

Wasm 到底和 Docker 有什么关系?
WASI 是WebAssembly 系统接口。您可以将其视为一组(类 POSIX)API,这些 API 允许 Wasm 运行时与操作系统交互。这像 JDK 类库吗?不完全的。它是一层薄薄的面向功能的 API,用于与操作系统交互。您可以在Mozilla 公告博客上阅读更多内容。

简而言之,这是拼图的最后一块:WASI 允许定义直接与操作系统交互的后端应用程序,无需任何额外层,也无需特殊 API。当前的努力是使 WASI 得到广泛采用,并在某种程度上成为后端开发的事实标准。

2019 年,Docker 创始人 Solomon Hykes 写道
如果 WASM+WASI 存在于 2008 年,我们就不需要创建 Docker。这就是它的重要性。服务器上的 Webassembly 是计算的未来。标准化的系统接口是缺失的一环。让我们希望 WASI 能够胜任这项任务!

WASI API包括诸如文件系统访问、网络、甚至线程API。这些API与运行时的低级能力携手合作,使其更容易移植到平台上。

WEBASSEMBLY 上的 JAVA 支持
目前,有多个项目和库处理 WebAssembly 和 Java。我整理了一份我在网上找到的清单。然而,此时,其中大部分是业余爱好项目。

在浏览器中运行 JAVA
许多项目将 Java 翻译成 WebAssembly。然而,它们中的大多数不会发出与更精简的 Wasm 运行时兼容的代码:通常,它们旨在在浏览器中运行。

这里还有一些以浏览器运行时为目标的项目的荣誉提名(在某些情况下具有实验性的 Wasm 支持):

  • J2CL(GWT 的后继者)是从 Java 到 JavaScript 的源到源转换器(即转译器),最近获得了对 Wasm 的支持。该编译器还对 GC 规范提供了前沿支持。
  • Bck2Brwsr是另一个针对 JavaScript 和浏览器的字节码编译器
  • Kotlin/Native也支持通过 LLVM 编译成 Wasm。它带有 Kotlin/Native 的所有注意事项(例如,它可能不支持您的所有 Java 库)
  • DoppioJVM是一个有趣的项目,我想提一下,因为它采用了一种完全不同的方法,类似于 Python 的方法:它不是将字节码编译为 Wasm,而是一个能够解释 JVM 字节码的浏览器内 VM(用 JavaScript 编写)。不幸的是,该项目目前无人维护。

在 JVM 上运行 WEBASSEMBLY
我们一直在谈论在 Wasm 运行时上运行 Java 程序。但当然,您也可能希望能够做相反的事情。平心而论,JVM 已经提供了相当多的编程语言,而大多数 Wasm 运行时提供的当前编程模型(具有手动内存管理)在托管在 JVM 上时似乎有点不合时宜。但为了完整起见,我还是想提及这些,因为总的来说,它们可能仍然很有趣。

  • 主要候选者显然是前面提到的 GraalVM 的 WebAssembly 解释器的 Truffle 实现,它受益于 GraalVM/Truffle 平台的所有 JIT 超级能力和多语言互操作性
  • asmble是一套工具,包括从 Wasm 到字节码的编译器和 Wasm 解释器
  • Happy New Moon With Report (JVM)是 JVM 的 WebAssembly 运行时(我把它包括在这个列表中是因为我喜欢这个愚蠢的名字!)
  • Katai WebAssembly是一个使用我目前正在维护的Katai Struct二进制解析器生成器编写的 Wasm 解析器(欢迎 PR!):这不一定意味着在 JVM 上运行Wasm,但当您希望能够操作时它实际上很有用或查询 Wasm 可执行文件以获取信息。事实上,Kaitai 语法允许为任何支持的语言生成二进制解析器,不仅是 Java,还包括 Python、Ruby、Go、C++ 和许多其他语言。


总结
Java-on-Wasm 还处于早期阶段,但我邀请您以开放的心态探索这个全新的世界:它可能会给您带来惊喜!