ScriptEngine 清除和处置

ScriptEngine clear and dispose

我的应用程序使用 ScriptEngine 为我的最终用户提供插件功能。

ScriptEngineManager engineManager = new ScriptEngineManager();
ScriptEngine engine = engineManager.getEngineByName("nashorn");

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

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

两个密切相关的问题:

问题是,我找不到任何看起来像 clear()dispose() 的方法。这是否意味着我目前的做法是正确的?

您可以使用单个引擎实例,但使用单独的 Bindings 个对象。 Bindings 充当顶级程序环境,因此如果您想将脚本评估为基本上是 "new global scope" 的脚本,那么您可以这样做。查看 javax.script API docs 了解如何执行此操作。您可以使用将 Bindings 作为第二个参数的 ScriptEngine.eval 或将 ScriptContext 作为第二个参数的

即使没有脚本代码从之前的评估中幸存下来,您也会节省一些初始化时间,因为脚本引擎已经预定义了各种 JavaScript 数据持有者 类 和 属性 地图 ("hidden classes").

另外:是的,一切都被垃圾收集了。不需要明确的 "disposal" API.

我只是想分享我自己测试的内容。这非常有道理,但对于那些仍然有疑问的人:创建的线程确实会继续 运行 如果您只是替换引擎实例:

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 !
    please, make it stop!!!
    Here's Johnny !
    Here's Johnny !
    Here's Johnny !
    ...

我猜想垃圾收集器可以清理脚本,但不能清理它们在上下文之外的行为。我认为创建的线程甚至没有以任何方式链接到脚本(即在它们的范围之外)。所以,我认为 jvm 不可能检测到或确定这些线程链接到替换的脚本并且可能会或可能不会停止。

但是对于一个 Whosebug 问题,这让我们走得太远了。 我们只关注 dispose/clear 绑定的能力(即 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,反射 类 也会被自动阻止。因此,您不必明确阻止这些包。