JavaFX、TreeTableView、RowFactory 和内存泄漏

JavaFX, TreeTableView, RowFactory, and memory leak

我正在使用 JavaFX TreeTableView 来显示销售订单。父行包含 "master" 记录,子行包含有关订单的信息,包括每个订购产品的行项目。我正在使用 RowFactory 根据其当前状态突出显示 table 中的行。 RowFactory 的代码以此代码开头:

        currentOrderTree.setRowFactory(new     Callback<TreeTableView<MmTicket>, TreeTableRow<MmTicket>>() {
            @Override
            public TreeTableRow<MmTicket> call(TreeTableView<MmTicket> p) {
                final TreeTableRow<MmTicket> row = new TreeTableRow<MmTicket>() {
                    @Override
                    protected void updateItem(MmTicket order, boolean empty) {
                        super.updateItem(order, empty);
                        if (order != null) {
                System.out.println("Calling rowFactory method for the " + ++rowcall + " time.");
                            if (packingListPendingList.containsKey(order.getSono())) {
                                if (!getStyleClass().contains("pending")) {
                                    getStyleClass().remove("working");
                                    getStyleClass().remove("fulfilled");
                                    getStyleClass().remove("completed");
                                    getStyleClass().add("pending");
                                    getStyleClass().remove("shipped");
                                }
....

如您所见,每次调用行工厂时回调 returns 一个新的 TreeTableRow。根据 JavaFX 8.0 文档,系统负责管理行的创建,并在适当的时候重用它们。 System.out.println 调用记录了方法被调用的次数。多次上下滚动table,以及销售数据库更新引起的数据刷新,导致该方法在较短的时间内被调用了数万次。随着最终用户广泛使用 TreeTableView,我在程序中的内存使用量持续增长。

分析应用程序表明 HashTable 和 PsuedoClass(css 东西,我相信)和诸如 byte[] 和 char[] 和 int[] 之类的东西正在使用大量内存. 我尝试了堆大小和垃圾收集器的不同组合,但最终结果总是一样的,因为应用程序最终用完了堆 space.

搜索答案表明有一些人遇到了这个问题,我发现如果我不做任何行突出显示,从而不调用行工厂,程序会更好管理内存。 有人对可能导致此问题的原因或可能的解决方案有任何见解吗?

如果跟踪正在创建的行对象的数量,您会发现数量并不多。显然,由于 updateItem(...) 被调用了很多次,因此发生了广泛的重用。

需要注意的一件事是 styleClass 是作为列表实现的,这当然允许重复。因此,当您无法控制调用方法的时间以及传递给它的参数时,您需要非常小心地管理方法中的内容,例如 updateItem(...) 。具体来说,您可能想确保 没有可能的 updateItem(...) 调用顺序 会导致相同的值被多次添加到样式 class .这包括使用 order=null 的调用。 (会发生什么,例如,在你的情况下,如果同一个单元格交替用于空单元格,order=null 和 "pending" 单元格?)看起来你正在通过测试(if (! getStyleClass().contains("pending"))) 但有些极端情况很容易被忽视。

尝试将 getStyleClass().size() 添加到调试输出中,看看它是否增长过度。

更简洁的解决方案可能是使用 CSS PseudoClasses 而不是样式 class。您可以按照

的方式做一些事情
PseudoClass pending = PseudoClass.getPseudoClass("pending");
PseudoClass working = PseudoClass.getPseudoClass("working");
PseudoClass fulfilled = PseudoClass.getPseudoClass("fulfilled");
PseudoClass completed = PseudoClass.getPseudoClass("completed");
PseudoClass shipped = PseudoClass.getPseudoClass("shipped");


@Override
protected void updateItem(MmTicket order, boolean empty) {
    super.updateItem(order, empty);

    pseudoClassStateChanged(pending, order != null && packingPendingList.containsKey(order.getSono()));
    pseudoClassStateChanged(working, order != null && packingWorkingList.containsKey(order.getSono()));
    pseudoClassStateChanged(fulfilled, order != null && packingFulfilledList.containsKey(order.getSono()));
    pseudoClassStateChanged(completed, order != null && packingCompletedList.containsKey(order.getSono()));
    pseudoClassStateChanged(shipped, order != null && packingShippedList.containsKey(order.getSono()));
}

(或一些适当的逻辑)。 PseudoClasses 只有两种状态(设置或未设置),因此这避免了管理样式 class 列表的所有问题。

你的 CSS 在这种情况下看起来像

.table-row-cell {
    /* basic styles... */
}

.table-row-cell:pending {
    /* styles specific to pending items */
}

.table-row-cell:working {
    /* styles specific to working items */
}

/* etc etc */

它可能不适用于您的用例,但您也可以匹配具有多个 PseudoClasses 集的节点

.table-row-cell:pending:working { /* ... */ }

如果您确实想坚持 classes 风格,请首先确保处理 null 大小写。你可能想要像

这样的东西
List<String> allStyles = Arrays.asList("pending", "working", "fulfilled", "completed", "shipped");

@Override
protected void updateItem(MmTicket order, boolean empty) {

    super.updateItem(order, empty);

    if (order == null) {
        getStyleClass().removeAll(allStyles);
    } else {
       // ...
    }
}

而不是

getStyleClass().remove("pending");

删除样式 class 列表中 "pending" 第一次出现 ,考虑

getStyleClass().removeAll(Collections.singletonList("pending"));

因为 removeAll(Collection) 方法删除了 提供的集合中包含的所有元素

同样,这可能不是问题的原因,但如果您在 updateItem() 方法中操作它,很容易让样式 class 列表无限增长。