当前位置: 首页 > 面试题库 >

垃圾回收后Javascript的JavaFx WebView回调失败

乐正迪
2023-03-14
问题内容

我目前正在使用基于JavaFX的应用程序,用户可以在其中与世界地图上标记的地点进行交互。为此,我使用的方法类似于[http://captaincasa.blogspot.de/2014/01/javafx-
and-osm-openstreetmap.html(1 ])中描述的方法。

但是,我面临着一个难以调试的问题,该问题与使用WebEngine的setMember()方法注入到嵌入式HTML页面的Javascript回调变量有关(另请参见[https://docs.oracle.com/javase/8/javafx
/embedded-browser-tutorial/js-
javafx.htm(2 ]),以获取正式教程)。

当运行程序一段时间后,回调变量会意外地失去其状态!为了演示这种行为,我开发了一个最小的工作/失败示例。我在Windows
10计算机上使用64位的jdk1.8.0_121。

JavaFx应用程序如下所示:

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import javafx.application.Application;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;

public class WebViewJsCallbackTest extends Application {

    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

    public static void main(String[] args) {
        launch(args);
    }

    public class JavaScriptBridge {
        public void callback(String data) {
            System.out.println("callback retrieved: " + data);
        }
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        WebView webView = new WebView();
        primaryStage.setScene(new Scene(new AnchorPane(webView)));
        primaryStage.show();

        final WebEngine webEngine = webView.getEngine();
        webEngine.load(getClass().getClassLoader().getResource("page.html").toExternalForm());

        webEngine.getLoadWorker().stateProperty().addListener((observableValue, oldValue, newValue) -> {
            if (newValue == State.SUCCEEDED) {
                JSObject window = (JSObject) webEngine.executeScript("window");
                window.setMember("javaApp", new JavaScriptBridge());
            }
        });

        webEngine.setOnAlert(event -> {
            System.out.println(DATE_FORMAT.format(new Date()) + " alerted: " + event.getData());
        });
    }

}

HTML文件“ page.html”如下所示:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<!-- use for in-browser debugging -->
<!-- <script type='text/javascript' src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script> -->
<script type="text/javascript">
    var javaApp = null;

    function javaCallback(data) {           
        try {
            alert("javaApp=" + javaApp + "(type=" + typeof javaApp + "), data=" + data);
            javaApp.callback(data);
        } catch (e) {
            alert("caugt exception: " + e);
        }
    }
</script>
</head>
<body>
    <button onclick="javaCallback('Test')">Send data to Java</button>
    <button onclick="setInterval(function(){ javaCallback('Test'); }, 1000)">Send data to Java in endless loop</button>
</body>
</html>

javaApp可以通过单击"Send data to Java in endless loop"按钮来观察回调变量的状态。它将继续尝试通过运行回调方法javaApp.callback,该方法在Java应用程序中生成一些日志记录消息。警报被用作附加的通讯渠道来备份事件(似乎一直有效,并且目前用作解决方法,但这并不是事情的原意……)。

如果一切都按预期工作,则每次打印类似以下几行的记录:

callback retrieved: Test
2017/01/27 21:26:11 alerted: javaApp=webviewtests.WebViewJsCallbackTest$JavaScriptBridge@51fac693(type=object), data=Test

但是,过了一会儿(从2到7分钟的任何时间),就不会再检索到任何回调,而只会打印类似以下行的日志:

2017/01/27 21:32:01 alerted: javaApp=undefined(type=object), data=Test

现在,打印变量将提供'undefined'而不是Java实例路径。一个奇怪的发现是的状态javaApp不是真正的“未定义”。使用typeof收益objectjavaApp === undefined计算结果为false。这符合以下事实:回调调用不会引发异常(否则,"caugt exception: "将打印以开头的警报)。

使用Java
VisualVM显示,故障时间恰巧与垃圾回收器被激活的时间一致。可以通过观察堆内存消耗来看出这一点,该消耗量从大约3%下降到2%。由于GC,从60MB到16MB。

那里发生了什么?您是否知道如何进一步调试该问题?我找不到任何相关的已知错误…

非常感谢您的建议!

PS:包含Javascript代码以通过Leaflet显示世界地图时,该问题的重现速度更快(参见[1])。大多数情况下,加载或移动地图会立即导致GC完成其工作。在调试此原始问题时,我将问题追溯到此处介绍的最小示例。


问题答案:

我通过bridge在Java中创建一个实例变量来解决该问题,该实例变量保存通过JavaScriptBridge发送给Javascript
的实例setMember()。这样,可以防止实例的垃圾回收。

相关代码段:

public class JavaScriptBridge {
    public void callback(String data) {
        System.out.println("callback retrieved: " + data);
    }
}

private JavaScriptBridge bridge;

@Override
public void start(Stage primaryStage) throws Exception {
    WebView webView = new WebView();
    primaryStage.setScene(new Scene(new AnchorPane(webView)));
    primaryStage.show();

    final WebEngine webEngine = webView.getEngine();
    webEngine.load(getClass().getClassLoader().getResource("page.html").toExternalForm());

    bridge = new JavaScriptBridge();
    webEngine.getLoadWorker().stateProperty().addListener((observableValue, oldValue, newValue) -> {
        if (newValue == State.SUCCEEDED) {
            JSObject window = (JSObject) webEngine.executeScript("window");
            window.setMember("javaApp", bridge);
        }
    });

    webEngine.setOnAlert(event -> {
        System.out.println(DATE_FORMAT.format(new Date()) + " alerted: " + event.getData());
    });
}

尽管代码现在可以顺利运行(也可以与Leaflet一起使用),但我仍然对这种意外行为感到恼火…

编辑:自Java
9起就记录了对此行为的解释(感谢@dsh的澄清注释!我当时正在使用Java
8,不幸的是手头没有此信息…)



 类似资料:
  • 问题内容: 考虑以下脚本: 关键是,在所有这些步骤之后,此python进程在我的机器上的内存使用率约为30%(Python 2.6.5,是否可应要求提供更多详细信息?)。这是top输出的摘录: 分别 : 根据该文档为: 由于特定的实现,尤其是和,并非某些空闲列表中的所有项目都可能无法释放。 这是否意味着,如果我(暂时)需要大量的不同或数字,我需要这个导出到C / C ++,因为Python的GC没

  • 垃圾回收 我们对生产中花了很多时间来调整垃圾回收。垃圾回收的关注点与Java大致相似,尽管一些惯用的Scala代码比起惯用的Java代码会容易产生更多(短暂的)垃圾——函数式风格的副产品。Hotspot的分代垃圾收集通常使这不成问题,因为短暂的(short-lived)垃圾在大多情形下会被有效的释放掉。 在谈GC调优话题前,先看看这个Attila的报告,它阐述了我们在GC方面的一些经验。 Scal

  • 对于开发者来说,JavaScript 的内存管理是自动的、无形的。我们创建的原始值、对象、函数……这一切都会占用内存。 当我们不再需要某个东西时会发生什么?JavaScript 引擎如何发现它并清理它? 可达性(Reachability) JavaScript 中主要的内存管理概念是 可达性。 简而言之,“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。 这里列出固有的可达值的

  • 垃圾收集,引用计数,显式分配 和所有的现代语言一样,OCaml提供垃圾收集器,所以你不用像C/C++一样显式地分配和释放内存。 JWZ在他的文章 "Java sucks" rant(Java蛋疼(怒)!): 第一个好家伙是Java没有 free()。其他的都没有所谓了。这几乎掩盖了所有的缺点,不管有多糟糕, 这个有点让后续文档基本都没有意义了,但是...(译注:但是啥大家自己看吧) OCaml的垃

  • 主要内容:垃圾回收器函数,实例Lua 采用了自动内存管理。 这意味着你不用操心新创建的对象需要的内存如何分配出来, 也不用考虑在对象不再被使用后怎样释放它们所占用的内存。 Lua 运行了一个垃圾收集器来收集所有死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。 Lua 实现了一个增量标记-扫描收集器。 它使用

  • 主要内容:1 什么是Java 垃圾回收,2 Java 垃圾回收的优势,3 如何取消对象引用,4 finalize()方法,5 gc()方法,6 Java 垃圾回收的例子1 什么是Java 垃圾回收 在Java中,垃圾意味着未引用的对象。 垃圾回收是自动回收运行时未使用的内存的过程。换句话说,这是销毁未使用对象的一种方法。 我们在C语言中使用free() 函数,在C ++中使用delete()。但是,在Java中它是自动执行的。因此,java提供了更好的内存管理。 2 Java 垃圾回收的优势 它