JavaFX, TreeTableView, RowFactory и утечка памяти
Я использую JavaFX TreeTableView для отображения заказов на продажу. Родительские строки содержат "основную" запись, а дочерние строки содержат информацию о заказе, включая позиции для каждого заказанного продукта. Я использую RowFactory, чтобы выделить строки в таблице на основе ее текущего состояния. Код для 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");
}
....
Как видите, Callback возвращает новый TreeTableRow каждый раз, когда вызывается фабрика строк. Согласно документации JavaFX 8.0, ответственность за управление созданием строк лежит на системе, и при необходимости используется повторно. Вызов System.out.println документирует, сколько раз вызывался метод. Прокрутка таблицы вверх и вниз несколько раз, а также обновления данных, вызванные обновлениями базы данных продаж, приводят к тому, что этот метод вызывается десятки тысяч раз за относительно короткий промежуток времени. Мое использование памяти в программе продолжает расти, поскольку TreeTableView широко используется конечными пользователями.
Профилирование приложения показывает, что HashTable и PsuedoClass используют очень большие объемы памяти (я полагаю, css) и такие вещи, как byte[], char[] и int[]. Я пробовал разные комбинации размеров кучи и сборщиков мусора, но конечный результат всегда один и тот же, в том смысле, что приложению в итоге не хватает места в куче.
Поиск ответов показал, что есть некоторые, кто испытывает эту проблему, и я обнаружил, что если я не делаю подсветку строк, тем самым не вызывая фабрику строк, программа намного лучше справляется с управлением памятью. У кого-нибудь есть понимание относительно того, что может быть причиной этой проблемы, или возможных решений?
1 ответ
Если вы отследите количество создаваемых объектов строк, вы обнаружите, что их не так много. Понятно, так как updateItem(...)
вызывается так много раз, что происходит обширное повторное использование.
Следует помнить, что styleClass
реализован в виде списка, который, конечно, допускает дублирование. Таким образом, вы должны быть очень осторожны при управлении его содержимым таким методом, как updateItem(...)
когда у вас нет контроля над тем, когда вызывается метод и какие параметры передаются ему. В частности, вы, вероятно, хотите убедиться, что нет никакой возможной последовательности вызовов updateItem(...)
это приведет к тому, что одно и то же значение будет добавлено в класс стиля несколько раз. Это включает вызовы с order=null
, (Что происходит, например, в вашем случае, если одна и та же ячейка поочередно используется для пустой ячейки, с order=null
и "ожидающая" ячейка?) Похоже, что вы проверяете это путем тестирования (if (! getStyleClass().contains("pending"))
) но есть некоторые угловые случаи, которые можно легко не заметить.
Попробуйте добавить getStyleClass().size()
к вашему выводу отладки, чтобы увидеть, если он чрезмерно растет.
В любом случае, более чистым решением может быть использование псевдо-классов CSS вместо класса style. Вы можете сделать что-то вроде
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()));
}
(или какая-то подходящая логика). Псевдоклассы имеют только два состояния (установлено или не установлено), что позволяет избежать всех проблем с управлением списком классов стилей.
Ваш 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 */
Это, вероятно, не применяется в вашем случае использования, но вы также можете сопоставить узлы с несколькими псевдоклассами, установленными с
.table-row-cell:pending:working { /* ... */ }
Если вы хотите придерживаться стилевых классов, сначала убедитесь, что вы обрабатываете нулевой регистр. Вы можете захотеть что-то вроде
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");
который удаляет первое вхождение "pending"
в списке классов стилей, рассмотрим
getStyleClass().removeAll(Collections.singletonList("pending"));
как removeAll(Collection)
Метод удаляет все элементы, содержащиеся в предоставленной коллекции.
Опять же, это не может быть причиной проблемы, но очень легко позволить списку классов стилей расти бесконечно, если вы манипулируете им в updateItem()
метод.