Java8之使用新JS解释器Nashorn编译Lambda表达式

标签: JAVA java8 Nashorn scala | 发表时间:2014-04-24 08:17 | 作者:踏雁寻花
出处:http://ifeve.com

Nashron.mainImage.fw_
原文链接  作者: Tal Weiss  CEO of  Takipi   译者:踏雁寻花,xbkaishui  校对:方腾飞

在最近的一篇 文章中,我了解了一下Java8和Scala是如何实现 Lambda 表达式的。正如我们所知道的,Java8不仅对javac编辑器做了很大改进,它还加入了一个全新的项目—Nashorn。这个新的解释器将会代替Java现有的Rhino解释器。据说它执行JavaScript的速度非常之快,就像世界上最快的跑车 V8s,所以,我觉得现在很有必要打开Nashorn源码,看看它是如何编译 Lambda 表达式的(着重于Java 和 Scala的对比)。

我们使用Java和Scala测试的 lambda表达式是非常相似的。

代码如下:

jcriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");

String js;

js = "var map = Array.prototype.map \n";
js += "var names = [\"john\", \"jerry\", \"bob\"]\n";
js += "var a = map.call(names, function(name) { return name.length() })\n";
js += "print(a)";

engine.eval(js);

感觉有点儿懵吧,继续往下看…

获取字节码

我们第一个任务就是获取JVM可以看懂的字节码。与Java和Scala编译器不同,这两个编译器是持久的(产生的.class文件、jar文件存放到磁盘),而Nashorn解释器则不同,Nashorn 编译后的数据都在内存中,然后把字节码支持传给JVM。我写了一个简单的Java代理来获得并保存生成的字节码,其实就是一个简单的javap反编译器了。

我看到Java8编译器使用了 invokeDynamic指令感到特别激动, invokeDynamic指令是在Java7中被引用的,目的是调用 Lambda函数。现在基于 Nashorn的工作都已经做完了,继续往下看。

读取字节码

invokeDynamic 指令:这个指令和我们整篇文章密切相关。Java 7 引入invokeDynamic 指令的目的是为了让开发人员可以自己去编写动态语言,决定在运行时如何链接代码,

对于像Java和Scala这样的静态语言来说,编译器在编译的时候就决定了哪一个方法将会被调用(而Java的多态性是通过JVM的一些的工具实现的),运行时的链接是通过 ClassLoaders加载类来完成的,甚至方法重载都是在编译时期完成的。

动态链接 VS 静态链接:很不幸,对于动态语言来说,静态解析也许是不可能的(JS就是一个很好的例子),当我们在Java语言中执行 obj.foo() 方法时,obj对象的类中也许有foo()方法,也许没有,而在一个类似JS的语言中,则取决于运行时obj实际对象的引用—静态编译器的噩梦。编译时链接在这个时候根本不起作用,不过 invokeDynamic指令可以做到。

InvokeDynamic 指令可以在运行时推迟返回这个语言的开发者的链接,所以它们能够根据自己的语义引导JVM调用哪一个方法,这是一个双赢的方案。JVM可以获得一个实际的链接方法,并进行优化,执行,而且语言开发者可以控制自己的解析方案。在 Takipi这个网站中我们必须努力去支持动态链接。

Nashorn解释器如何链接:Nashorn很好的利用了这一点。让我们看一看一个例子来理解Nashorn是如何工作的。代码的作用是用来检索JS数组类的值:

  invokedynamic 0 "dyn:getProp|getElem|getMethod:prototype":(Ljava/lang/Object;)Ljava/lang/Object;

Nashorn需要JVM在运行时传递一个String类型参数,并返回一个方法,这个方法接受一个Object类型的参数,同时返回一个Object类型的对象。只要JVM获得这个方法的一个句柄(handle),就会链接。

这个方法负责返回一个句柄(就是一个引导程序的方法–bootstrap method),在.class文件中的一个特殊部分被指定,持有一系列的引导方法。你看到的0是表的索引,JVM调用方法获得方法的句柄,JVM就是用这个句柄进行链接的。

我认为Nashorn项目开发团队做了一件很爽的事情,那就是不再需要他们自己编写解析和链接代码的库了,而是集成了 dynalink项目,这个开源项目是为了在一个统一的平台上将动态语言链接成代码。这就是为什么在每一个String之前都有一个”dyn:”前缀的原因了。

实际的工作流

既然我们已经完成了Nashorn所使用的方法,下面就让我们看一看实际流。为了简洁,我去掉了一些不重要的代码。整个代码可以在这里下载。 1、这段儿代码作用是加载JS数组函数映射到脚本中

  //加载JS数组(load JS array)
invokedynamic 0 "dyn:getProp|getElem|getMethod:Array":(Ljava/lang/Object;)Ljava/lang/Object;

//加载数组中的原型元素(load its prototype element)
invokedynamic 0 "dyn:getProp|getElem|getMethod:prototype":(Ljava/lang/Object;)Ljava/lang/Object;

//加载map方法(load the map method)
invokedynamic 0 "dyn:getProp|getElem|getMethod:map":(Ljava/lang/Object;)Ljava/lang/Object;

//set到本地(set it to the map local)
invokedynamic 0 #0:"dyn:setProp|setElem:map":(Ljava/lang/Object;Ljava/lang/Object;)V

2、分配names 数组

  //把names数组分成JS对象(allocate the names array as a JS object)
invokestatic jdk/nashorn/internal/objects/Global.allocate:([Ljava/lang/Object;)Ljdk/nashorn/internal/objects/NativeArray;

//将对象放到names中(places it into names)
invokedynamic 0 #0:"dyn:setProp|setElem:names":(Ljava/lang/Object;Ljava/lang/Object;)V

invokedynamic 0 #0:"dyn:getProp|getElem|getMethod:names":(Ljava/lang/Object;)Ljava/lang/Object;

3、找到并加载Lambda 函数

  //为在运行时被Nashorn编译的脚本加载常量(load the constants field for this script compiled and filled at runtime by Nashorn)
getstatic constants

//将2放到栈顶,Nashorn将会把句柄放到lambda代码中(refer to the 2nd entry, where Nashorn will place a handle to the lambda code)
iconst_2

//从常量数组中获取它(get it from the constants array)
aaload

//检察它是否是一个JS函数对象(ensure it’s a JS function object)
checkcast class jdk/nashorn/internal/runtime/RecompilableScriptFunctionData

4、通过传入参数names和Lambda调用map函数,把结果存放到a中

  //调用map函数,把names和栈中返回的Lambda函数当做参数传入(call the map function, passing it names and the Lambda function from the stack)
invokedynamic 0 #1:"dyn:call":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljdk/nashorn/internal/runtime/ScriptFunction;)Ljava/lang/Object;

//把返回结果存放到a中(put the result in a)
invokedynamic 0 #0:"dyn:setProp|setElem:a":(Ljava/lang/Object;Ljava/lang/Object;)V

5、找到print函数,并用a调用它

  //加载print函数(load the print function)
invokedynamic 0 #0:"dyn:getMethod|getProp|getElem:print":(Ljava/lang/Object;)Ljava/lang/Object;

//加载a(load a)
invokedynamic 0 #0:"dyn:getProp|getElem|getMethod:a":(Ljava/lang/Object;)Ljava/lang/Object;

//调用print函数(call print on it)
invokedynamic 0 #2:"dyn:call":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

lambda函数和脚本一样被编译并放到相同的类中,作为一个private方法。这个和Java8中lambdas表达式是非常相似的。代码非常简单,我们加载String,并找到lengh()方法,然后调用它。

  //加载参数名称(Load the name argument (var #1))
aload_1

//找到length()方法(find its length() function)
invokedynamic 0 "dyn:getMethod|getProp|getElem:length":(Ljava/lang/Object;)Ljava/lang/Object;

//调用length()(call length)
invokedynamic 0 "dyn:call":(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

//返回结果(return the result)
areturn

奖励环节-最后的字节码

到目前为止,我们所完成的代码不能在JVM运行时执行。要记住,每一个invokeDynamic 指令将会被处理成一个物理字节码方法,然后由JVM将其编译成机器语言并执行。 为了看到JVM执行的真正字节码,我使用了一个技巧,我在类中使用一个简单的方法wrap(String s)去调用length()方法。这就需要我放一个断点,这样就可以看到JVM执行时的堆栈情况。

代码如下: js += “var a = map.call(names, function(name) { return Java.type(“LambdaTest”).wrap(name.length()) })”;

这是wrap方法: public static int wrap(String s) { return s.length(); }

堆栈的调用完整情况请看 这里

 

(全文完)如果您喜欢此文请点赞,分享,评论。





您可能感兴趣的文章

相关 [java8 js 解释器] 推荐:

Java8之使用新JS解释器Nashorn编译Lambda表达式

- - 并发编程网 - ifeve.com
原文链接  作者: Tal Weiss  CEO of  Takipi   译者:踏雁寻花,xbkaishui  校对:方腾飞. 在最近的一篇 文章中,我了解了一下Java8和Scala是如何实现 Lambda 表达式的. 正如我们所知道的,Java8不仅对javac编辑器做了很大改进,它还加入了一个全新的项目—Nashorn.

Java8集合中的Lambda表达式

- - 四火的唠叨
文章系本人原创,转载请保持完整性并注明出自 《四火的唠叨》. 本文翻译自《 Java 8 Explained: Applying Lambdas to Java Collections》. Lambdas表达式是Java 8的主题,在Java平台上我们期待了很久. 但是,如果如果我们不在集合中使用它的话,就损失了很大价值.

屌丝就爱尝鲜头——java8初体验 - laozhu1124

- - 博客园_首页
  Java8已经推出,让我们看看他的魅力.   Java8是由Oracle(甲骨文)公司与2014年3月27日正式推出的. Java8同时推出有3套语言系统,分别是Java SE8、Java SE Emebbled 8、Java ME8.   Java SE8较以往的系统增强的功能有:.   ①增强了对集合式操作语言——lambda表达式的支持,“Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数.

【译】Java8 之后对新开发者非常友好的特性

- - crossoverJie's Blog
在这篇文章中,我将描述自 Java8 依赖对开发者来说最重要也最友好的特性,之所以选择 Java8 ,那是因为它依然是目前使用最多的版本. 具体可见这个调查报告:. Switch 表达式 (JDK 12). 使用 switch 表达式,你可以定义多个 case 条件,并使用箭头 -> 符号返回值,这个特性在 JDK12 之后启用,它使得 switch 表达式更容易理解了.

WebView JS 交互

- - ITeye博客
WebView加jquery做页面会怎么样呢. // 创建WebView对象. // 把programList添加到js的全局对象window中,. // 这样就可以使用window.programList来获取数据. * 定义js回调java函数. // 绑定键盘的向上,向下按钮事件触发相应的js事件.

解释器Interpreter

- - 企业架构 - ITeye博客
所谓解释器模式就是定义一个语言的文法,并建立一个解释器来解释该语言中的句子. 比如:taglib标签,正则表达式都是采用的解释器. 解释器不仅仅只定义一种语法语义,更可以使用一个抽象语法树定义一个语言实例.     AbstractExpression: 抽象表达式. 声明一个抽象的解释操作,该接口为抽象语法树中所有的节点共享.

JS游戏引擎

- 米随随 - HTML5研究小组
If you don’t have anything better to do and want to help fellow redditors interested in JS game dev out, feel free to fork the list and modify it as you like.

來源請求.js

- 红烧鲤鱼 - Blog: timdream
很早以前就想講了,但講了大概又會被戰. 相較於英文維基百科,中文維基百科在社會和歷史條目充滿了 systemic bias. 但是那些主觀論述又不是編輯者有意加進去的,而是某種編輯者存在的社會所給予的暗示(Inception?)與集體共識,而不是原本百科全書應該有的可驗證的事實. 因為是暗示又是共識,所以有自覺的百科編輯者反而是少數;中文維基只好長成現在這個樣子了.

Js删除节点

- - JavaScript - Web前端 - ITeye博客
 方式一:传this参数调用方法:.  方式二:js方法中通过选择器获取节点:. //此处删除的是a节点 }. 方式三:通过jQuery方式获取节点:(尚未测试,有待测试. 此处a标签传this到js中,js通过this(即a节点)取parent(即p节点). (1)p.remove();可直接删除整个p节点.

JS游戏引擎列表

- sku - 酷壳 - CoolShell.cn
这里有一个网址收集了关于JS游戏引擎开发库的一个列表,转过来. 关于使用JS和HTML5做的一些小游戏,可参见《HTML5 小游戏展示》. Name Latest Release License Type Notes The Render Engine 1.5.3 MIT 跨浏览器; 大规模 API; 开源. 2 gameQuery 0.5.1 CC BY-SA 2.5 和 jQuery 一起使用 gTile 0.0.1 Tile based.