ScriptEngine清除和处理

7
我的应用程序使用一个 ScriptEngine 来为我的最终用户提供插件功能。
ScriptEngineManager engineManager = new ScriptEngineManager();
ScriptEngine engine = engineManager.getEngineByName("nashorn");

每当用户对脚本进行更改时,应用程序会用新实例替换引擎实例。

String newScript = ...;
engine = engineManager.getEngineByName("nashorn");
engine.eval(newScript);

两个紧密相关的问题:

  • 我应该尝试重复使用引擎并对其执行某种clear()吗?

  • 如果我只是用新实例替换我的引擎,我是否应该以某种方式处理前一个实例,以避免内存泄漏?(例如,我可以想象用户可能会创建一个启动线程的脚本。)

问题是,我找不到任何类似于clear()dispose()的方法。这是否意味着我的当前方法是正确的?

2个回答

7
您可以使用单个引擎实例,但使用单独的Bindings对象。 Bindings充当顶级程序环境,因此如果要将脚本评估为基本上是“新全局范围”,那么可以这样做。请查看javax.scriptAPI docs以了解如何执行此操作。您可以使用ScriptEngine.eval,它接受Bindings作为第二个参数,或者使用接受ScriptContext作为第二个参数的方法。
即使没有来自先前评估的脚本代码,您也会节省一些初始化时间,因为脚本引擎已经预定义了各种JavaScript数据持有者类和属性映射(“隐藏类”)。
另外:是的,所有内容都是垃圾回收的。不需要显式的“处理”API。

一切都很清晰,但是关于“无需处理”的部分,请参见我下面的答案:显然Java不会为您清理正在运行的线程。 - bvdb

6

我想分享一下我亲自测试的内容。这很有道理,但对于那些还有疑问的人来说:如果只是替换引擎实例,则创建的线程会继续运行

public static void main(String[] args) throws ScriptException {
    ScriptEngineManager manager = new ScriptEngineManager();

    String script =
            "new java.lang.Thread(function() {\n" +
            "  for(;;) {" +
            "    print('Here\\'s Johnny !');" +
            "    java.lang.Thread.sleep(1000);" +
            "  }\n" +
            "}).start();";

    ScriptEngine engine = manager.getEngineByName("nashorn");
    try {
        engine.eval(script);
    } catch (ScriptException e) {
        e.printStackTrace();
    }
    
    // replace engine
    engine = manager.getEngineByName("nashorn");
    engine.eval("print('please, make it stop!!!');");
    
    // please collect !!!
    System.gc();
}

输出:

    Here's Johnny!
    请停止!!!
    Here's Johnny!
    Here's Johnny!
    Here's Johnny!
    ...

我猜垃圾回收器可以清除脚本,但不能清除脚本上下文之外的操作。我认为创建的线程甚至没有与脚本以任何方式链接(即在其范围之外)。因此,我认为JVM根本无法检测或决定这些线程是否与替换的脚本相关联并且可能会停止。

但是这让我们离一个stackoverflow问题太远了。让我们只关注处置/清除绑定的能力(即ScriptContext)。

阻塞nashorn脚本中的java线程:

一种可能的解决方案是缩小可用功能的范围。以下是避免创建线程的几种方法:

以下禁用所有Java功能:

// the option -nj is short for --no-java
ScriptEngine engine = new NashornScriptEngineFactory().getScriptEngine("-nj");

但是你也可以使用ClassFilter来禁用特定的类。
    ScriptEngine engine = new NashornScriptEngineFactory().getScriptEngine((className) -> {
        if ("java.lang.Thread".equals(className)) return false;
        if ("java.lang.Runnable".equals(className)) return false;
        if ("java.util.Timer".equals(className)) return false;
        if (className.startsWith("java.util.concurrency")) return false;
        if (className.startsWith("javafx")) return false;
        if (className.startsWith("javax.swing")) return false;
        if (className.startsWith("java.awt")) return false;
        return true;
    });

注意:一旦您定义了一个ClassFilter,反射类也会自动被阻止。因此,您不必显式地阻止那些包。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接