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 列表无限增长。
我正在使用 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 列表无限增长。