JavaFX VirtualFlow 中的内存泄漏?

huangapple go评论55阅读模式
英文:

JavaFX Memory Leak in VirtualFlow?

问题

我在VirtualFlow中遇到了一种行为,不确定是内存泄漏还是设计如此。

在一个TableView中,我期望当TableRow实例不再使用时,它们应该能够被垃圾回收。然而,当我按照以下步骤操作时:

**步骤#1:**我打开一个带有空数据的TableView的舞台。我检查TableRow的实例的内存,没有找到任何实例(这是我期望的结果)。

**步骤#2:**我填充了TableView的一些数据,这将根据所需的空间生成TableRows。在我的场景中,假设它生成了38个TableRow对象的实例。

**步骤#3:**我清除了TableView中的项目,占位符消息显示出来。当我检查TableRow的实例(在执行多次GC后),它仍然在VirtualFlow的cells ArrayLinkedlist中保持着所有38个TableRows的引用,如下图所示:

JavaFX VirtualFlow 中的内存泄漏?

这是内存泄漏还是已经考虑过的预期行为?

这是我用于检查上述场景的代码:

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TableViewMemoryLeakDemo extends Application {

    public static void main(String... a) {
        Application.launch(a);
    }

    @Override
    public void start(final Stage primaryStage) {
        final ObservableList<TableDataObj> items = FXCollections.observableArrayList();
        final TableView<TableDataObj> table = buildTable();
        table.setItems(items);

        Button clear = new Button("Clear All");
        clear.setOnAction(e -> {
            items.clear();
        });

        Button add = new Button("Add 50 Rows");
        add.setOnAction(e -> {
            for (int i = 0 + 1; i < 50; i++) {
                final String firstName = "First Name " + i;
                final String lastName = "Last Name " + i;
                final String city = "City " + i;
                items.add(new TableDataObj(i, firstName, lastName, city));
            }
        });
        HBox row = new HBox(add, clear);
        row.setSpacing(10);

        final VBox root = new VBox(row, table);
        root.setSpacing(10);
        root.setPadding(new Insets(10));
        VBox.setVgrow(table, Priority.ALWAYS);

        final Scene sc = new Scene(root, 1200, 950);
        primaryStage.setScene(sc);
        primaryStage.setTitle("TableView MemoryLeak Demo " + System.getProperty("javafx.runtime.version"));
        primaryStage.show();
    }

    private TableView<TableDataObj> buildTable() {
        final TableColumn<TableDataObj, Integer> idCol = new TableColumn<>();
        idCol.setText("Id");
        idCol.setCellValueFactory(param -> param.getValue().idProperty().asObject());
        idCol.setPrefWidth(100);

        final TableColumn<TableDataObj, String> fnCol = new TableColumn<>();
        fnCol.setText("First Name");
        fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());
        fnCol.setPrefWidth(150);

        final TableColumn<TableDataObj, String> lnCol = new TableColumn<>();
        lnCol.setText("Last Name");
        lnCol.setCellValueFactory(param -> param.getValue().lastNameProperty());
        lnCol.setPrefWidth(150);

        final TableColumn<TableDataObj, String> cityCol = new TableColumn<>();
        cityCol.setText("City");
        cityCol.setCellValueFactory(param -> param.getValue().cityProperty());
        cityCol.setPrefWidth(150);

        final TableView<TableDataObj> tableView = new TableView<>();
        tableView.getColumns().addAll(idCol, fnCol, lnCol, cityCol);
        return tableView;
    }

    /**
     * Data object.
     */
    class TableDataObj {
        private final IntegerProperty id = new SimpleIntegerProperty();
        private final StringProperty firstName = new SimpleStringProperty();
        private final StringProperty lastName = new SimpleStringProperty();
        private final StringProperty city = new SimpleStringProperty();

        public TableDataObj(final int i, final String fn, final String ln, final String cty) {
            setId(i);
            setFirstName(fn);
            setLastName(ln);
            setCity(cty);
        }

        public StringProperty cityProperty() {
            return city;
        }

        public StringProperty firstNameProperty() {
            return firstName;
        }

        public int getId() {
            return id.get();
        }

        public IntegerProperty idProperty() {
            return id;
        }

        public StringProperty lastNameProperty() {
            return lastName;
        }

        public void setCity(final String city1) {
            city.set(city1);
        }

        public void setFirstName(final String firstName1) {
            firstName.set(firstName1);
        }

        public void setId(final int idA) {
            id.set(idA);
        }

        public void setLastName(final String lastName1) {
            lastName.set(lastName1);
        }
    }
}
英文:

I am experiencing a behavior in VirtualFlow and am not sure if it is a memory leak or if it is designed that way.

In a TableView, I expect that when the TableRow instances are not used, they should be able to be garbage collected. However, when I follow these steps:

Step #1: I opened a stage with a TableView with no data in it. I checked the memory for live instances of TableRow and see no instances (which is what I expect).

Step #2: I populated the TableView with some data, which generates the TableRows as per the required space. In my scenario, let's say it generated 38 instances of TableRow objects.

Step #3: I cleared the items in the TableView and the placeholder message is displayed. When I check for the TableRow instances (after performing multiple GCs), it still has all 38 references of TableRows strongly held in VirtualFlow's cells ArrayLinkedlist, as shown in the following image:

JavaFX VirtualFlow 中的内存泄漏?

Is this a memory leak or is it expected behavior that has already been considered?

Here is the code I used to check the above scenario:

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TableViewMemoryLeakDemo extends Application {

    public static void main(String... a) {
        Application.launch(a);
    }

    @Override
    public void start(final Stage primaryStage) {
        final ObservableList&lt;TableDataObj&gt; items = FXCollections.observableArrayList();
        final TableView&lt;TableDataObj&gt; table = buildTable();
        table.setItems(items);

        Button clear = new Button(&quot;Clear All&quot;);
        clear.setOnAction(e -&gt; {
            items.clear();
        });

        Button add = new Button(&quot;Add 50 Rows&quot;);
        add.setOnAction(e -&gt; {
            for (int i = 0 + 1; i &lt; 50; i++) {
                final String firstName = &quot;First Name &quot; + i;
                final String lastName = &quot;Last Name &quot; + i;
                final String city = &quot;City &quot; + i;
                items.add(new TableDataObj(i, firstName, lastName, city));
            }
        });
        HBox row = new HBox(add, clear);
        row.setSpacing(10);

        final VBox root = new VBox(row, table);
        root.setSpacing(10);
        root.setPadding(new Insets(10));
        VBox.setVgrow(table, Priority.ALWAYS);

        final Scene sc = new Scene(root, 1200, 950);
        primaryStage.setScene(sc);
        primaryStage.setTitle(&quot;TableView MemoryLeak Demo &quot; + System.getProperty(&quot;javafx.runtime.version&quot;));
        primaryStage.show();
    }

    private TableView&lt;TableDataObj&gt; buildTable() {
        final TableColumn&lt;TableDataObj, Integer&gt; idCol = new TableColumn&lt;&gt;();
        idCol.setText(&quot;Id&quot;);
        idCol.setCellValueFactory(param -&gt; param.getValue().idProperty().asObject());
        idCol.setPrefWidth(100);

        final TableColumn&lt;TableDataObj, String&gt; fnCol = new TableColumn&lt;&gt;();
        fnCol.setText(&quot;First Name&quot;);
        fnCol.setCellValueFactory(param -&gt; param.getValue().firstNameProperty());
        fnCol.setPrefWidth(150);

        final TableColumn&lt;TableDataObj, String&gt; lnCol = new TableColumn&lt;&gt;();
        lnCol.setText(&quot;Last Name&quot;);
        lnCol.setCellValueFactory(param -&gt; param.getValue().lastNameProperty());
        lnCol.setPrefWidth(150);

        final TableColumn&lt;TableDataObj, String&gt; cityCol = new TableColumn&lt;&gt;();
        cityCol.setText(&quot;City&quot;);
        cityCol.setCellValueFactory(param -&gt; param.getValue().cityProperty());
        cityCol.setPrefWidth(150);

        final TableView&lt;TableDataObj&gt; tableView = new TableView&lt;&gt;();
        tableView.getColumns().addAll(idCol, fnCol, lnCol, cityCol);
        return tableView;
    }

    /**
     * Data object.
     */
    class TableDataObj {
        private final IntegerProperty id = new SimpleIntegerProperty();
        private final StringProperty firstName = new SimpleStringProperty();
        private final StringProperty lastName = new SimpleStringProperty();
        private final StringProperty city = new SimpleStringProperty();

        public TableDataObj(final int i, final String fn, final String ln, final String cty) {
            setId(i);
            setFirstName(fn);
            setLastName(ln);
            setCity(cty);
        }

        public StringProperty cityProperty() {
            return city;
        }

        public StringProperty firstNameProperty() {
            return firstName;
        }

        public int getId() {
            return id.get();
        }

        public IntegerProperty idProperty() {
            return id;
        }

        public StringProperty lastNameProperty() {
            return lastName;
        }

        public void setCity(final String city1) {
            city.set(city1);
        }

        public void setFirstName(final String firstName1) {
            firstName.set(firstName1);
        }

        public void setId(final int idA) {
            id.set(idA);
        }

        public void setLastName(final String lastName1) {
            lastName.set(lastName1);
        }
    }
}

答案1

得分: 5

根据您的要求,以下是代码部分的翻译:

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 * @see https://stackoverflow.com/q/76487091/230513
 */
public class TableViewLeak extends Application {

    public static void main(String... a) {
        Application.launch(a);
    }

    @Override
    public void start(final Stage primaryStage) {
        final ObservableList<TableDataObj> items = FXCollections.observableArrayList();
        final TableView<TableDataObj> table = buildTable();
        table.setItems(items);

        Button add = new Button("Add 50 Rows");
        add.setOnAction(e -> {
            for (int i = 0 + 1; i < 50; i++) {
                final String firstName = "First Name " + i;
                final String lastName = "Last Name " + i;
                final String city = "City " + i;
                items.add(new TableDataObj(i, firstName, lastName, city));
            }
        });
        Button clear = new Button("Clear All");
        clear.setOnAction(e -> {
            items.clear();
        });
        Button remove = new Button("Refresh");
        remove.setOnAction((e) -> {
            table.refresh();
        });

        HBox row = new HBox(add, clear, remove);
        row.setSpacing(10);
        final VBox root = new VBox(row, table);
        root.setSpacing(10);
        root.setPadding(new Insets(10));
        VBox.setVgrow(table, Priority.ALWAYS);

        final Scene sc = new Scene(root);
        primaryStage.setScene(sc);
        primaryStage.setTitle("TableView MemoryLeak Demo "
            + System.getProperty("javafx.runtime.version"));
        primaryStage.show();
    }

    private TableView<TableDataObj> buildTable() {
        final TableColumn<TableDataObj, Integer> idCol = new TableColumn<>();
        idCol.setText("Id");
        idCol.setCellValueFactory(param -> param.getValue().idProperty().asObject());
        idCol.setPrefWidth(100);

        final TableColumn<TableDataObj, String> fnCol = new TableColumn<>();
        fnCol.setText("First Name");
        fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());
        fnCol.setPrefWidth(150);

        final TableColumn<TableDataObj, String> lnCol = new TableColumn<>();
        lnCol.setText("Last Name");
        lnCol.setCellValueFactory(param -> param.getValue().lastNameProperty());
        lnCol.setPrefWidth(150);

        final TableColumn<TableDataObj, String> cityCol = new TableColumn<>();
        cityCol.setText("City");
        cityCol.setCellValueFactory(param -> param.getValue().cityProperty());
        cityCol.setPrefWidth(150);

        final TableView<TableDataObj> tableView = new TableView<>();
        tableView.getColumns().addAll(idCol, fnCol, lnCol, cityCol);
        return tableView;
    }

    /**
     * Data object.
     */
    private static final class TableDataObj {

        private final IntegerProperty id = new SimpleIntegerProperty();
        private final StringProperty firstName = new SimpleStringProperty();
        private final StringProperty lastName = new SimpleStringProperty();
        private final StringProperty city = new SimpleStringProperty();

        public TableDataObj(final int i, final String fn, final String ln, final String cty) {
            setId(i);
            setFirstName(fn);
            setLastName(ln);
            setCity(cty);
        }

        public StringProperty cityProperty() {
            return city;
        }

        public StringProperty firstNameProperty() {
            return firstName;
        }

        public int getId() {
            return id.get();
        }

        public IntegerProperty idProperty() {
            return id;
        }

        public StringProperty lastNameProperty() {
            return lastName;
        }

        public void setCity(final String city1) {
            city.set(city1);
        }

        public void setFirstName(final String firstName1) {
            firstName.set(firstName1);
        }

        public void setId(final int idA) {
            id.set(idA);
        }

        public void setLastName(final String lastName1) {
            lastName.set(lastName1);
        }
    }
}

如果您有其他需要翻译的内容,请提供具体的文本。

英文:

As noted in comments above and illustrated here, many JavaFX view components provide for flyweight rendering: a factory method is invoked to create a small number of instances that can be reused to render visible content. TableView typically requires only one TableRow per visible (or partially visible) row. Maximizing the enclosing stage illustrates the effect. As @James_D observes, "Surely this is the expected behavior?"

In normal use, the underlying data would be updated in situ, as shown in your example. Should the need arise in the larger context of VirtualFlow, the variation below adds a <kbd>Refresh</kbd> button to examine the effect of invoking refresh() on the table. This allows unused table rendering apparatus to be collected in a future pass. As a test, enlarge the stage, and note the larger number of rows. Shrink the stage, click <kbd>Refresh</kbd> and request garbage collection. Note the reduced number of TableRow instances.

<!-- language: java -->


import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* @see https://stackoverflow.com/q/76487091/230513
*/
public class TableViewLeak extends Application {
public static void main(String... a) {
Application.launch(a);
}
@Override
public void start(final Stage primaryStage) {
final ObservableList&lt;TableDataObj&gt; items = FXCollections.observableArrayList();
final TableView&lt;TableDataObj&gt; table = buildTable();
table.setItems(items);
Button add = new Button(&quot;Add 50 Rows&quot;);
add.setOnAction(e -&gt; {
for (int i = 0 + 1; i &lt; 50; i++) {
final String firstName = &quot;First Name &quot; + i;
final String lastName = &quot;Last Name &quot; + i;
final String city = &quot;City &quot; + i;
items.add(new TableDataObj(i, firstName, lastName, city));
}
});
Button clear = new Button(&quot;Clear All&quot;);
clear.setOnAction(e -&gt; {
items.clear();
});
Button remove = new Button(&quot;Refresh&quot;);
remove.setOnAction((e) -&gt; {
table.refresh();
});
HBox row = new HBox(add, clear, remove);
row.setSpacing(10);
final VBox root = new VBox(row, table);
root.setSpacing(10);
root.setPadding(new Insets(10));
VBox.setVgrow(table, Priority.ALWAYS);
final Scene sc = new Scene(root);
primaryStage.setScene(sc);
primaryStage.setTitle(&quot;TableView MemoryLeak Demo &quot;
+ System.getProperty(&quot;javafx.runtime.version&quot;));
primaryStage.show();
}
private TableView&lt;TableDataObj&gt; buildTable() {
final TableColumn&lt;TableDataObj, Integer&gt; idCol = new TableColumn&lt;&gt;();
idCol.setText(&quot;Id&quot;);
idCol.setCellValueFactory(param -&gt; param.getValue().idProperty().asObject());
idCol.setPrefWidth(100);
final TableColumn&lt;TableDataObj, String&gt; fnCol = new TableColumn&lt;&gt;();
fnCol.setText(&quot;First Name&quot;);
fnCol.setCellValueFactory(param -&gt; param.getValue().firstNameProperty());
fnCol.setPrefWidth(150);
final TableColumn&lt;TableDataObj, String&gt; lnCol = new TableColumn&lt;&gt;();
lnCol.setText(&quot;Last Name&quot;);
lnCol.setCellValueFactory(param -&gt; param.getValue().lastNameProperty());
lnCol.setPrefWidth(150);
final TableColumn&lt;TableDataObj, String&gt; cityCol = new TableColumn&lt;&gt;();
cityCol.setText(&quot;City&quot;);
cityCol.setCellValueFactory(param -&gt; param.getValue().cityProperty());
cityCol.setPrefWidth(150);
final TableView&lt;TableDataObj&gt; tableView = new TableView&lt;&gt;();
tableView.getColumns().addAll(idCol, fnCol, lnCol, cityCol);
return tableView;
}
/**
* Data object.
*/
private static final class TableDataObj {
private final IntegerProperty id = new SimpleIntegerProperty();
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final StringProperty city = new SimpleStringProperty();
public TableDataObj(final int i, final String fn, final String ln, final String cty) {
setId(i);
setFirstName(fn);
setLastName(ln);
setCity(cty);
}
public StringProperty cityProperty() {
return city;
}
public StringProperty firstNameProperty() {
return firstName;
}
public int getId() {
return id.get();
}
public IntegerProperty idProperty() {
return id;
}
public StringProperty lastNameProperty() {
return lastName;
}
public void setCity(final String city1) {
city.set(city1);
}
public void setFirstName(final String firstName1) {
firstName.set(firstName1);
}
public void setId(final int idA) {
id.set(idA);
}
public void setLastName(final String lastName1) {
lastName.set(lastName1);
}
}
}

huangapple
  • 本文由 发表于 2023年6月16日 13:05:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/76487091.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定