50字范文,内容丰富有趣,生活中的好帮手!
50字范文 > Java 8:在新的Nashorn JS引擎中编译Lambda表达式

Java 8:在新的Nashorn JS引擎中编译Lambda表达式

时间:2018-08-06 00:11:07

相关推荐

Java 8:在新的Nashorn JS引擎中编译Lambda表达式

在最近的一篇文章中,我了解了Java 8和Scala如何实现Lambda表达式。 众所周知,Java 8不仅引入了对Javac编译器的改进,而且还引入了全新的解决方案-Nashorn。

这个新引擎旨在替代Java现有JavaScript解释器Rhino。 这为我们带来了JVM的前列,当谈到在速度与世界的V8引擎执行JavaScript,在那里(我希望我们会最终得到过去那种汽车地毯的事情 :))所以,我认为这将是一个很好的时机,也可以通过深入了解Nashorn,看看它如何编译Lambda表达式(尤其是与Java和Scala相比)。

我们将要看的lambda表达式类似于我们用Java和Scala测试过的表达式。

这是代码:

ScriptEngineManager 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会编译内存中的所有内容,并将字节码直接传递给JVM。 幸运的是,我们已经有了Java代理来进行救援。 我编写了一个简单的Java代理来捕获并保留生成的字节码。 从那里开始,有一个简单的javap可以打印代码。

如果您还记得的话,我很高兴看到新的Java 8编译器如何使用Java 7中引入的invokeDynamic指令链接到Lambda函数代码。 好吧,与Nashorn一起,他们真的参加了比赛。 现在,一切都完全基于它。 看看下面。

读取字节码

invokeDynamic。 就像我们都在同一页面上一样,Java 7中添加了invokeDynamic指令,以允许人们编写自己的动态语言来在运行时决定如何链接代码。

对于Java和Scala之类的静态语言,编译器在编译时决定要调用哪种方法(在JVM运行时的帮助下,用于多态)。 运行时链接是通过标准ClassLoader完成的,以查找类。 甚至方法重载解析之类的事情都在编译时完成。

动态与静态链接。 不幸的是,对于本质上更具动态性的语言(并且JS是一个很好的例子),静态解析可能是不可能的。 当我们在Java中说obj.foo()时,obj类要么具有foo()方法,要么没有。 在像JS这样的语言中,它将依赖于运行时obj引用的实际对象,这是静态编译器的噩梦。 在这种情况下,编译时的链接方法行不通。 但是invokeDynamic确实可以。

InvokeDynamic允许在运行时将链接推迟回该语言的编写者,因此他们可以根据自己的语言语义来指导JVM确定要调用哪种方法。 这是双赢的局面。 JVM获得了一种链接,优化和执行的实际方法,并且语言制造商可以控制其解析度。 动态链接是我们在Takipi中必须努力支持的工作 。

Nashorn如何链接

Nashorn确实有效地利用了这一点。 让我们看一下示例,以了解其工作原理。 这是Lambda代码中的第一条invokeDynamic指令,用于检索JS Array类的值–

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

Nashorn要求JVM在运行时将其传递给该字符串,作为交换,它将向一个接受对象并返回一个对象的方法返回一个句柄。 只要JVM获得了这种方法的句柄,它就可以链接。

.class文件的特殊部分中指定了负责返回此句柄的方法(也称为引导程序方法),该文件中包含可用的引导程序方法列表。 您看到的0值是该方法在该表中的索引,JVM将调用该索引以获取将链接到的方法句柄。

在我看来,Nashorn的人们做了一件很酷的事情,他们没有编写自己的库来解析和链接代码, 而是继续集成了dynalink这个开源项目,该项目旨在帮助动态语言基于统一平台链接代码。 这就是为什么您在每个字符串的开头看到“ dyn:”前缀的原因。

实际流量

现在我们已经掌握了Nashorn所使用的方法,现在让我们看一下实际流程。 为了简洁起见,我删除了一些说明。 完整的代码可以在这里找到。

1.第一组指令将数组映射函数加载到脚本中。

//load JS arrayinvokedynamic 0 "dyn:getProp|getElem|getMethod:Array":(Ljava/lang/Object;)Ljava/lang/Object;//load its prototype elementinvokedynamic 0 "dyn:getProp|getElem|getMethod:prototype":(Ljava/lang/Object;)Ljava/lang/Object;//load the map methodinvokedynamic 0 "dyn:getProp|getElem|getMethod:map":(Ljava/lang/Object;)Ljava/lang/Object;//set it to the map localinvokedynamic 0 #0:"dyn:setProp|setElem:map":(Ljava/lang/Object;Ljava/lang/Object;)V

2.接下来,我们分配名称数组

//allocate the names array as a JS objectinvokestatic jdk/nashorn/internal/objects/Global.allocate:([Ljava/lang/Object;)Ljdk/nashorn/internal/objects/NativeArray;//places it into namesinvokedynamic 0 #0:"dyn:setProp|setElem:names":(Ljava/lang/Object;Ljava/lang/Object;)Vinvokedynamic 0 #0:"dyn:getProp|getElem|getMethod:names":(Ljava/lang/Object;)Ljava/lang/Object;

3.查找并加载Lambda函数

//load the constants field for this script compiled and filled at runtime by Nashorngetstatic constants//refer to the 2nd entry, where Nashorn will place a handle to the lambda codeiconst_2//get it from the constants arrayaaload//ensure it’s a JS function objectcheckcast class jdk/nashorn/internal/runtime/RecompilableScriptFunctionData

4.使用名称和Lambda调用map,然后将结果放入

//call the map function, passing it names and the Lambda function from the stackinvokedynamic 0 #1:"dyn:call":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljdk/nashorn/internal/runtime/ScriptFunction ;)Ljava/lang/Object;//put the result in ainvokedynamic 0 #0:"dyn:setProp|setElem:a":(Ljava/lang/Object;Ljava/lang/Object;)V

5.找到打印功能并在打印机上调用

//load the print functioninvokedynamic 0 #0:"dyn:getMethod|getProp|getElem:print":(Ljava/lang/Object;)Ljava/lang/Object;//load ainvokedynamic 0 #0:"dyn:getProp|getElem|getMethod:a":(Ljava/lang/Object;)Ljava/lang/Object;// call print on itinvokedynamic 0 #2:"dyn:call":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

lambda函数本身被编译并作为私有函数放在与脚本相同的类中。 这与Java 8 lambda非常相似。 代码本身很简单。 我们加载字符串,找到其长度函数并调用它。

//Load the name argument (var #1)aload_1//find its length() functioninvokedynamic 0 "dyn:getMethod|getProp|getElem:length":(Ljava/lang/Object;)Ljava/lang/Object;//call lengthinvokedynamic 0 "dyn:call":(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;//return the resultareturn

奖金回合–最终的字节码

到目前为止,我们一直在处理的代码实际上并不是JVM在运行时将执行的代码。 请记住,每个invokeDynamic指令都将解析为物理字节码方法,然后JVM将其编译为机器代码并执行。

为了查看JVM运行的实际字节码,我使用了一个简单的技巧。 我在类中使用简单的Java方法调用包装了对length()的调用。 这使我可以放置一个断点,并查看JVM执行以进入Lambda的最终调用堆栈。

这是代码–

js += "var a = map.call(names, function(name) {return Java.type("LambdaTest”).wrap(name.length())})";

包好了–

public static int wrap(String s){return s.length();}

现在玩游戏。 该堆栈中有几帧? 想一想。 如果您猜不到<100 –您欠我一杯啤酒。 完整的调用堆栈可以在这里找到。

之所以如此,也是非常有趣的原因,这是关于即将发布的全新帖子的故事。

参考:Java 8:来自Takipi博客的JCG合作伙伴 Iris Shoor 在New Nashorn JS Engine中编译Lambda表达式 。

翻译自: //02/java-8-compiling-lambda-expressions-in-the-new-nashorn-js-engine.html

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。