之前的项目里面大量使用了Nashorn引擎,目的是很多需要动态执行的代码放到了JavaScript里面,这样在用户那边比较好调试。但是因为性能的问题遇到了几个坑,最后经过一段时间,最终解决了,形成了这篇文章。

使用预编译JS来加速

在实际的应用中,基本上JS是不变的,变的只是调用的参数。于是乎可以通过预编辑,来进行加速。这样可以省略每次编译的耗时。代码如下。

String script = " var x = a + 1; " +

" var y = x * 2 + m; " +

" var z = y * 3 - n; " +

" z;" +

final CompiledScript compiled = ((Compilable)scriptEngine).compile(script);

调用的时候,可以直接

try {

Bindings bindings = new SimpleBindings();

bindings.put("a", v);

bindings.put("m", v + 1);

bindings.put("n", v + 2) ;

o = (Double)compiled.eval(bindings);

} catch (ScriptException e) {

e.printStackTrace();

}

预编辑JS遇到的问题

这样的写法是没有问题的,但是在压力比较大的时候,CPU比较高,还是比较慢。经过跟踪,发现大量的时间浪费在JDK初始化Nashorn的执行上下文了。

经过查看JDK的源代码,Nashorn里面其实提供了共享上下文的方法。修改代码为:

NashornScriptEngineFactory factory = null;

for (ScriptEngineFactory f : sm.getEngineFactories()) {

if (f.getEngineName().equalsIgnoreCase("Oracle Nashorn")) {

factory = (NashornScriptEngineFactory)f;

break;

}

}

String[] stringArray = new String[]{"-doe", "--global-per-engine"};

ScriptEngine scriptEngine = factory.getScriptEngine(stringArray);

final CompiledScript compiled = ((Compilable)scriptEngine).compile(script);

主要的是初始化参数中的--global-per-engine这个参数。

但是这样写,在并发执行的时候,会遇到问题。测试代码如下:

public class TestScript1 {

static ExecutorService pool = Executors.newCachedThreadPool();

static String script = " var x = a + 1; " +

" var y = x * 2 + m; " +

" var z = y * 3 - n; " +

" z;";

public static void main(String[] argu) throws ScriptException {

ScriptEngineManager sm = new ScriptEngineManager();

NashornScriptEngineFactory factory = null;

for (ScriptEngineFactory f : sm.getEngineFactories()) {

if (f.getEngineName().equalsIgnoreCase("Oracle Nashorn")) {

factory = (NashornScriptEngineFactory)f;

break;

}

}

String[] stringArray = new String[]{"-doe", "--global-per-engine"};

ScriptEngine scriptEngine = factory.getScriptEngine(stringArray);

final CompiledScript compiled = ((Compilable)scriptEngine).compile(script);

long l0 = System.currentTimeMillis();

AtomicInteger counter = new AtomicInteger();

for (int i=0; i<1024; i++) {

final int v = i;

pool.submit(() -> {

Bindings bindings = new SimpleBindings();

bindings.put("a", v);

bindings.put("m", v + 1);

bindings.put("n", v + 2) ;

int expected = getExpected(v, v + 1, v + 2);

Double o = null;

try {

o = (Double)compiled.eval(bindings);

if (o.intValue() == (expected)) {

// System.out.println("OK " + o);

counter.incrementAndGet();

} else {

System.err.println("FAILED!!!" + o + " " + (v + 1));

}

} catch (ScriptException e) {

e.printStackTrace();

}

});

}

pool.shutdown();

try {

boolean r = pool.awaitTermination(5, TimeUnit.SECONDS);

System.out.println("Terminated " + r);

} catch (InterruptedException e) {

e.printStackTrace();

}

long l1 = System.currentTimeMillis();

System.out.println(l1 - l0 + " ms");

System.out.println("Total OK = " + counter.intValue());

}

private static int getExpected(int a, int m, int n) {

int x = a + 1;

int y = x * 2 + m;

int z = y * 3 - n;

return z;

}

}

在上面的代码中,同时启动1024个线程进行模拟计算,同时和期望的值进行比较(一个JS版本,一个Java版本)。最后发现结果大部分都错了。为什么?

原因是,测试的JS是上下文相关的。

观察下JS,其中有三个上下文中的变量:a, m, n.

var x = a + 1;

var y = x * 2 + m;

var z = y * 3 - n;

这三个变量是通过Java代码中的

Bindings bindings = new SimpleBindings();

bindings.put("a", v);

bindings.put("m", v + 1);

bindings.put("n", v + 2) ;

来设置的。那么自然的,多线程执行的时候,后面的调用就有可能覆盖前面的调用。所以JS在执行的过程中,值乱套了。

解决并发的问题

经过尝试,上面并发的问题可以用下面的方式来解决。

首先,将值包装在一个参数对象中。

Mapparameters = new HashMap<>();

parameters.put("b", v);

parameters.put("c", v + 1);

parameters.put("d", v + 2) ;

bindings.put("parameters", parameters);

其次,也是最重要的,要保证JS的代码是上下文无关代码(只和调用参数相关,和全局变量无关)

static String script = "function xx(a, m, n) { " +

" var x = a + 1; " +

" var y = x * 2 + m; " +

" var z = y * 3 - n; " +

" return z;" +

"} " +

"xx(parameters.b, parameters.c, parameters.d);";

如上,这段Script的结果只和调用时传入的参数有关,和其它的无关。

整个测试程序如下:

public class TestPoolledScript {

static ExecutorService pool = Executors.newCachedThreadPool();

static String script = "function xx(a, m, n) { " +

" var x = a + 1; java.lang.Thread.sleep(2); " +

" var y = x * 2 + m; java.lang.Thread.sleep(3);" +

" var z = y * 3 - n; java.lang.Thread.sleep(1);" +

" return z;" +

"} " +

"xx(parameters.b, parameters.c, parameters.d);";

public static void main(String[] argu) throws ScriptException {

ScriptEngineManager sm = new ScriptEngineManager();

NashornScriptEngineFactory factory = null;

for (ScriptEngineFactory f : sm.getEngineFactories()) {

if (f.getEngineName().equalsIgnoreCase("Oracle Nashorn")) {

factory = (NashornScriptEngineFactory)f;

break;

}

}

String[] stringArray = new String[]{"-doe", "--global-per-engine"};

ScriptEngine scriptEngine = factory.getScriptEngine(stringArray);

final CompiledScript compiled = ((Compilable)scriptEngine).compile(script);

long l0 = System.currentTimeMillis();

AtomicInteger counter = new AtomicInteger();

for (int i=0; i<1024; i++) {

final int v = i;

pool.submit(() -> {

Bindings bindings = new SimpleBindings();

Mapparameters = new HashMap<>();

parameters.put("b", v);

parameters.put("c", v + 1);

parameters.put("d", v + 2) ;

bindings.put("parameters", parameters);

int expected = getExpected(v, v + 1, v + 2);

Double o = null;

try {

o = (Double)compiled.eval(bindings);

if (o.intValue() == (expected)) {

// System.out.println("OK " + o);

counter.incrementAndGet();

} else {

System.err.println("FAILED!!!" + o + " " + (v + 1));

}

} catch (ScriptException e) {

e.printStackTrace();

}

});

}

pool.shutdown();

try {

boolean r = pool.awaitTermination(5, TimeUnit.SECONDS);

System.out.println("Terminated " + r);

} catch (InterruptedException e) {

e.printStackTrace();

}

long l1 = System.currentTimeMillis();

System.out.println(l1 - l0 + " ms");

System.out.println("Total OK = " + counter.intValue());

}

private static int getExpected(int a, int m, int n) {

int x = a + 1;

int y = x * 2 + m;

int z = y * 3 - n;

return z;

}

}

这样的结果是完全正确的。

在我的机器上执行效率是之前的写法(没有加--global-per-engine)的四到五倍。

没有加--global-per-engine 1024个线程并发大概是2秒钟。

加上之后大概是500毫秒。

总结

最后,我们可以通过下面的方式来提高Nashorn执行JS的性能:

使用预编译;

使用--global-per-engine参数,同时修改JS代码,做到上下文无关。

java nashorn使用场景_优化Java调用Nashorn引擎的性能相关推荐

  1. java transient 应用场景_关于java:transient关键字的用途是什么以及何时使用它?...

    本问题已经有最佳答案,请猛点这里访问. Possible Duplicate: what does the keyword"transient" means in java? 号 ...

  2. java项目----教务管理系统_基于Java的教务管理系统

    java项目----教务管理系统_基于Java的教务管理系统 2022-04-22 18:18·java基础 最近为客户开发了一套学校用教务管理系统,主要实现学生.课程.老师.选课等相关的信息化管理功 ...

  3. java 堆大小的最大值_优化Java堆大小的5个技巧

    优化Java堆大小的5个技巧 摘要:Java堆容量不足可以对性能造成很大影响,这样无疑就给程序带来不可必要的麻烦,本文总结了影响Java堆容量不足的五大原因以及巧妙地去优化? 本文作者Pierre是一 ...

  4. java 异常补偿解决_第三方接口调用异常补偿机制实现实例记录

    背景: 我们的组件(简称A),在业务链中属于数据支撑节点.其中与组件B存在接口同步数据的直接关系(API接口直接调用进行数据交互) 问题: 我们的上游有另一个组件C(带有界面),调用A(us)进行数据 ...

  5. JAVA 油站管理系统_基于JAVA的全国加油站[实时油价]接口调用代码实例

    代码描述:基于JAVA的全国加油站[实时油价]接口调用代码实例 接口地址:https://www.juhe.cn/docs/api/id/7 1.[代码][Java]代码 import java.io ...

  6. java执行python脚本_通过Java调用Python脚本

    在进行开发的过程中,偶尔会遇到需要使用Java调用Python脚本的时候,毕竟Python在诸如爬虫,以及科学计算等方面具有天然的优势.最近在工作中遇到需要在Java程序中调用已经写好的Python程 ...

  7. Java并发编程实战_福州java编程实战培训班排名

    如何选择福州java培训中心? 在福州,如果想迅速掌握java开发,参加福州java培训班无疑是一种非常有效的方式.但是,市场上有这么多的java培训机构,我们在选择的时候难免会眼花缭乱.福州java ...

  8. java中变量 关键字_基于java的voliate关键字详解

    voliate关键字的作用: 一.内存可见性 基于缓存一致性协议,当用voliate关键字修饰的变量改动时,cpu会通知其他线程,缓存已被修改,需要更新缓存.这样每个线程都能获取到最新的变量值. 二. ...

  9. java 编译 解释执行_关于Java的编译执行与解释执行

    编程语言分为低级语言和高级语言,机器语言.汇编语言是低级语言,C.C++.java.python等是高级语言. 机器语言是最底层的语言,能够直接执行.而我们编写的源代码是人类语言, 计算机只能识别某些 ...

最新文章

  1. 运行ORB-SLAM笔记_编译篇(一)
  2. win7中安装mysql_windows7下安装Mysql5.6数据库图文教程(压缩包安装)
  3. NodeJS http服务端获取POST请求数据
  4. hdu 4739 状压DP
  5. Kubernetes 新玩法:在 yaml 中编程
  6. python 中 try、except、finally的用法(异常处理)
  7. AIX Study之--AIX网卡配置管理(ent0、en0、et0)
  8. cmd命令大全 DOS窗口命令
  9. Mat详解-OpenCV
  10. python可变参数教学,Python函数可变参数详解
  11. php xml 添加节点 出问题,PHP往XML中添加节点的方法
  12. 3.eclipse对mysql云数据库编程增删改查
  13. 通过反射获取类的所有属性和方法
  14. 我们都是孩子。』凄美的爱情青春
  15. cmock学习笔记20190821
  16. matlab定积分矩形法实验,MATLAB实验三 定积分的近似计算
  17. 学完 Fluent 官方基础教程,你离一名合格Fluent 流体工程师还有多远?
  18. 批处理之计划任务at和schtasks
  19. 路径规划算法3.1 人工势场法APF
  20. 局域网DNS服务器搭建

热门文章

  1. 未来币nxt 启动分析(1)
  2. 常用网页使用js技巧收集(200多个)经典
  3. BurpSuite实战二之BurpSuite代理和浏览器设置
  4. 【JavaScript周报】#574: Etsy从React迁移至Preact
  5. 良心推荐两个学习数据结构和算法的利器,让学习像呼吸一样轻松
  6. easyx库写浪漫流星雨
  7. hexdump使用小技巧
  8. java redis监控工具_聊聊redis的监控工具
  9. 出现“发生访问冲突”类型错误的解决方法
  10. td中bug处理过程_TD使用过程中常见问题(整理中)