JEP 174:Nashorn JavaScript 引擎
概述
设计并实现一个新的轻量级、高性能的 JavaScript 实现,并将其集成到 JDK 中。这个新的引擎将通过现有的 javax.script
API 提供给 Java 应用程序使用,同时也会更广泛地通过一个新的命令行工具提供支持。
目标
- Nashorn 将基于 ECMAScript-262 第 5.1 版语言规范,并且必须通过 ECMAScript-262 的兼容性测试。
- Nashorn 将支持
javax.script
(JSR 223) API。 - 将提供从 JavaScript 调用 Java 代码以及从 Java 调用 JavaScript 代码的支持,其中包括与 JavaBeans 的直接映射。
- Nashorn 将定义一个新的命令行工具
jjs
,用于在 "shebang" 脚本、here 文档和编辑字符串中评估 JavaScript 代码。 - Nashorn 应用程序的性能和内存使用应显著优于 Rhino。
- Nashorn 不会引入任何额外的安全风险。
- 提供的库应在本地化环境下正常运行。
- 错误消息和文档将实现国际化。
非目标
- Nashorn 将仅支持 ECMAScript-262 第 5.1 版本,不会支持第 6 版本的任何特性,也不会支持其他 JavaScript 实现提供的任何非标准特性。
- Nashorn 不会包含浏览器插件 API。
- Nashorn 不会包含对 DOM/CSS 或任何相关库的支持(例如,jQuery、Prototype 或 Dojo)。
- Nashorn 不会包含直接的调试支持。
动机
Rhino 的性能已经远远落后于其他 JavaScript 引擎。为了提高性能,此时 Rhino 必须进行重写,以其代码生成器取代解释器,从而充分利用 JVM。与其对非常古老的 Rhino 代码进行大规模重写,我们选择从头开始。
推动 JVM 成为 Java 以外其他语言的可行平台,这也符合 Java 社区的利益。这促进了技术的发展,并吸引了新的开发者。
描述
Nashorn 引擎分五个阶段对 JavaScript 源代码进行求值:词法分析器 -> 解析器 -> 代码生成 -> 加载 -> 运行时。
词法分析器接收一个 Unicode 字符数组(源代码),并将其转换为一系列词法单元或标记的流;例如,数字、字符串、标识符或特殊字符。
Nashorn 使用基于 ECMAScript 262 语言规范的递归下降解析器。解析器接收词法分析器生成的标记流,并收集与语言语法匹配的标记。当识别出语法单元时,解析器会构建 JavaScript 代码的中间表示(AST/IR)。
代码生成器获取 AST/IR 并生成 JVM 字节码。执行时,该字节码实现了原始 JavaScript 源代码的语义。
代码生成分为两个步骤。第一步将 AST/IR 转换为更接近 JVM 指令集的内容。这种转换包括控制流的转换、将表达式简化为基本操作,以及简化调用表达式。这一步还负责定义用于管理数据使用、空间和类型的符号。
第二步通过 ASM 库将 AST/IR 转换为字节码。ASM 生成实际的字节码以形成脚本类。这个类会被缓存在内存中,以供脚本加载器后续使用。
一旦脚本类生成后,就需要将其安装到 JVM 中。这是通过一个安全的自定义类加载器使用 defineClass
来完成的。当在生成的代码中遇到特殊的 Nashorn 对象类时,这个加载器也会用于合成这些类。一旦脚本类被加载,其 runMethod
方法就会被调用。
执行代码需要若干库的支持。
主运行时库包括直接支持代码执行的方法,例如,toString
和 toNumber
所需的类型转换例程,以及像 allocateArray
这样的分配例程。
链接库包含使用 java.lang.invoke
API(JSR 292)协助绑定 invokedynamic
调用的方法。绑定过程非常复杂,包括搜索正确的方法、定义保护条件以确保调用点保持正确,以及在某些变化需要不同的正确方法时重新链接。链接器还管理着一个调用历史记录,以便在连续的重新链接时加快查找速度。
JavaScript 对象库包含对所有标准 JavaScript 对象的支持,例如,Object
、Function
、Number
、String
、Date
、Array
、RegExp
等等。它还包括用于字符串操作、复杂数学运算、应用函数等功能。
Nashorn 使用 invokedynamic
来实现其所有的调用。如果某个调用的接收者是一个 Java 对象,Nashorn 会尝试将该调用绑定到适当的 Java 方法,而不是 JavaScript 函数。Nashorn 在解析方法时拥有完全的自主权。例如,如果它在接收者中找不到某个字段,它会寻找等效的 Java Bean 方法。这一过程对于从 JavaScript 调用 Java 的操作来说是完全透明的。
Java 开发者可以使用 javax.script
(JSR 223)API 来评估和回调 JavaScript 代码。
替代方案
Rhino 有着悠久的历史,其源代码也是如此。大多数 Rhino 代码支持一种解释执行模型。为了显著提升性能,执行模型需要充分利用 Hotspot 解释器/编译器。
其它 JavaScript 引擎(例如 V8 和 Nitro)虽然能提供良好的性能,但需要第二个 VM(更多的代码和内存),并且需要一个桥接 API 来与 Java 通信。
测试
JCK
现有的 javax.script
JCK 测试应该已经足够。Nashorn 只是另一个可以通过 javax.script
API 访问的脚本引擎。Nashorn 本身并未暴露任何新的 Java 标准 API,因此不需要额外的 JCK 测试。
功能/单元测试
现有的测试包括:
- test262 (ecmascript.org)
- ES5conform
这些测试覆盖了大部分功能区域,但这些测试必须适配到 Nashorn 的基于 TestNG 的测试套件中。并非所有这些测试都适用,因为 Nashorn 符合 ECMAScript-262 第 5.1 版标准,并且仅有少量扩展。特别是,mozilla_js_tests 涵盖了许多 Nashorn 不支持的 Mozilla 特定功能。因此,需要进行一些子集划分和适配工作,才能在 Nashorn 的测试框架内使用这些测试。
现有的 javax.script
API 和 jrunscript
命令行工具的单元测试也可以用于 Nashorn。
Nashorn 代码库中的其他单元/回归测试
这些是 Nashorn 开发人员在实现功能和修复错误的过程中开发的测试。
性能测试
- Sunspider JavaScript 核心引擎性能测试 (webkit.org)
- v8 (Octane) 性能测试
进行了一些适配工作,以便在 Nashorn 测试框架下运行这些测试。
注意:这些性能测试不包括浏览器 API 访问(DOM/CSS 等)。像 Dromaeo 这样的性能测试套件确实提供了此类测试,但它们超出了 Nashorn 的当前范围。
安全测试
Nashorn 会编译 JavaScript 源代码并生成 Java 类。这些类由一个特殊的类加载器加载。Nashorn 允许从 JavaScript 中调用 Java。必须小心确保这些生成的类遵循 Java 安全模型。需要进行测试以确保在安全管理器下,Nashorn 也是安全的。对于由 Nashorn 生成的脚本类,不应拥有任何编译后的 Java 类文件无法获得的额外权限。
影响
- 兼容性:Nashorn 不会实现当前 JDK 中 Rhino 引擎所包含的任何 ECMAScript-262 标准之外的功能。
- 安全性:需要测试
- 性能/可扩展性:需要测试
- 国际化/本地化:是
- 可移植性:否;Nashorn 不包含任何原生代码。
- 文档:是