Java调用Nashorn引擎的性能优化

项目里面涉及到根据具体值计算逻辑表达式或者计算表达式的结果,所以使用了Nashorn引擎,近期发现执行性能比较低,最后经过一段时间的查阅,最终解决了问题,形成了这篇文章。

Nashorn JavaScript 引擎是 Java 8 的一部分, Nashorn 扩展了 Java 在 JVM 上运行动态 JavaScript 脚本的能力。

在实际应用中,我们是表达式基本不变,传入调用的参数,可以采用预编译来加速。

实际操作中发现不使用预编译也可以,因为开启共享上下文之后会缓存最近的预编译模板。

ScriptEngineFactory factory = new NashornScriptEngineFactory();
ScriptEngine scriptEngine = factory.getScriptEngine();
// 编写js代码
String script = "var a = x + 1;  " +
        " var b = y * 2 + 3; " +
        " var c = a + b; " +
        " c;" ;
//预编译
final CompiledScript compiled = ((Compilable)scriptEngine).compile(script);

调用

Bindings bindings = new SimpleBindings();
bindings.put("x", 1);
bindings.put("y", 2);
System.out.println(compiled.eval(bindings));

这种方法在服务器压力比较大的时候,CPU使用高,效率还是慢,经过追查,发现大量时间浪费在JDK初始化Nashorn的执行上下文,查看Nashorn的源码,里面提供了共享上下文的方法

NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
String[] params = new String[]{"--global-per-engine"};
ScriptEngine scriptEngine = factory.getScriptEngine(params);

就是这个初始化参数--global-per-engine

见源码

//初始化参数由args传入
NashornScriptEngine(NashornScriptEngineFactory factory, String[] args, final ClassLoader appLoader, final ClassFilter classFilter) {
    assert args != null : "null argument array";

    this.factory = factory;
    final Options options = new Options("nashorn");
    options.process(args);
    final ErrorManager errMgr = new ThrowErrorManager();
    this.nashornContext = (Context)AccessController.doPrivileged(new PrivilegedAction<Context>() {
        public Context run() {
            try {
                return new Context(options, errMgr, appLoader, classFilter);
            } catch (RuntimeException var2) {
                if (Context.DEBUG) {
                    var2.printStackTrace();
                }

                throw var2;
            }
        }
    }, CREATE_CONTEXT_ACC_CTXT);
//初始化参数传入--global-per-engine,则此处为true,否则为false
    this._global_per_engine = this.nashornContext.getEnv()._global_per_engine;
    this.global = this.createNashornGlobal(this.context);
    this.context.setBindings(new ScriptObjectMirror(this.global, this.global), 100);

使用之一为

private Global getNashornGlobalFrom(ScriptContext ctxt) {
// 为true,则直接返回共享的上下文
    if (this._global_per_engine) {
        return this.global;
    } else {
        Bindings bindings = ctxt.getBindings(100);
        if (bindings instanceof ScriptObjectMirror) {
            Global glob = this.globalFromMirror((ScriptObjectMirror)bindings);
            if (glob != null) {
                return glob;
            }
        }

        Object scope = bindings.get("nashorn.global");
        if (scope instanceof ScriptObjectMirror) {
            Global glob = this.globalFromMirror((ScriptObjectMirror)scope);
            if (glob != null) {
                return glob;
            }
        }

        ScriptObjectMirror mirror = this.createGlobalMirror(ctxt);
        bindings.put("nashorn.global", mirror);
        return mirror.getHomeGlobal();
    }
}

带来的并发问题

NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
String[] params = new String[]{"--global-per-engine"};
ScriptEngine scriptEngine = factory.getScriptEngine(params);

String script = " var a = x + 1;  " +
        " var b = y + 3; " +
        " var c = a + b; " +
        " c;";

final CompiledScript compiled = ((Compilable) scriptEngine).compile(script);
ExecutorService pool = Executors.newFixedThreadPool(10);
AtomicInteger integer = new AtomicInteger();
for (int i = 0; i < 1024; i++) {
    final int v = i;
    pool.submit(() -> {
        Bindings bindings = new SimpleBindings();
        bindings.put("x", v);
        bindings.put("y", 2 * v);
        try {
            Double d = (Double) compiled.eval(bindings);
            if (d == (3 * v + 4)) {
                integer.incrementAndGet();
            }
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    });
}
pool.shutdown();
try {
    pool.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("total true : " + integer);

多线程模拟计算,发现部分结果(对比Java代码计算和JS代码计算)为错误的,这是因为上下文是相关的,JS中三个上下文相关变量为a、b、c,在多线程的执行下,会相互覆盖。

解决并发问题

使用function函数或者函数表达式来保证JS中的变量是上下文无关

NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
System.out.println(factory.getEngineVersion());
String[] params = new String[]{"--global-per-engine"};
ScriptEngine scriptEngine = factory.getScriptEngine(params);
//使用function
String script = "function eval(a, b) {return a + b + 4;} eval(x, y);";
//或 String script = "x + y + 4";
final CompiledScript compiled = ((Compilable) scriptEngine).compile(script);
ExecutorService pool = Executors.newFixedThreadPool(10);
AtomicInteger integer = new AtomicInteger();
for (int i = 0; i < 1024; i++) {
    final int v = i;
    pool.submit(() -> {
        Bindings bindings = new SimpleBindings();
        bindings.put("x", v);
        bindings.put("y", 2 * v);
        try {
            Double d = (Double) compiled.eval(bindings);
            if (d == (3 * v + 4)) {
                integer.incrementAndGet();
            }
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    });
}
pool.shutdown();
try {
    pool.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("total true : " + integer);

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

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

总结

  1. 使用预编译提高Nashorn执行JS的性能
  2. 使用--global-per-engine参数,共享上下文提高性能
  3. 使用function定义JS代码,做到上下文无关,解决并发问题
  4. 注意不同版本,高并发下执行导致的ScriptException:ReferenceError
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值