使用Java8的Nashorn弥补Node.js密集计算的缺陷

NodeJS带来的原生异步并发与事件驱动编程模型得到认可,但是因为其单线程缘故,不能简单方便地从事密集计算,而java优势是多线程并发,Java 8又引入了Lambda表达式,使得Java多线程并发在处理高CPU负载的计算上既强大又方便,那么我们是否对于Javascript中那些密集计算,比如对集合中的map操作,或聚合操作reduce使用Java实现呢?

答案是可以的,利用Java8的Javascript引擎 Nashorn。比如我们可以将Js如下的lambda在JVM中实现:


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.;

Nashorn是用来替代原来Java中的Javacript解释器Rhino, 执行JS速度大大提升,能够使用V8s环境,下面看看如何编译lambda表达式。

不同于Java 和Scala 编译器,是将文件编译成持久文件,如.class文件等,Nashorn是在内存中编译,然后将字节码直接传递到JVM,Java 8使用invokerDynamic来连接Lambda函数代码。

invokerDynamic是在Java 7中加入允许程序员写动态语言,然后决定在运行时如何连接这些代码。

对于Java和Scala语言,编译器是在编译时间决定哪个方法被调用,运行时是通过标准的ClassLoader来寻找相应的类,即使像方法过载解决也是编译时间实现的。

对于动态语言,如js,静态的解决办法就不适用了,当我们在Java中说obj.foo()时,其实是obj有一个类的方法为foo(),而JS运行时是依赖被obj引用的实际对象,这对于静态语言是一个梦魇,因为编译和运行是分开的,编译时连接到这个对象但是却没有运行。但是invokeDynamic可以做到。

invokeDynamic能够推迟这种联动到运行时间,这样在运行时它能指导JVM调用哪个方法。静态语言和动态语言在这点上是双赢的。JVM得到要连接的实际方法,然后优化执行。

Nashorn也是这样高效地实现连接,下面看个例子可以了解如何工作的,下面是返回JS数组值:
invokedynamic 0 "dyn:getProp|getElem|getMethod:prototype":(Ljava/lang/Object;)Ljava/lang/Object;

Nashorn要求JVM在运行传递给它这个字符串,作为交换,它会返回一个方法的一个处理器,用于接收对象和返回一个,只要JVM得到这个方法的一个处理器,它就能连接。

详细流畅比较复杂,可见文章:Java 8: Compiling Lambda Expressions in The New Nashorn JS Engine