You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX VirtualFlow中TableRow未被GC回收:是否为内存泄漏?

TableView清空数据后TableRow实例未被垃圾回收,是内存泄漏还是预期行为?

我在使用VirtualFlow时遇到一个情况:在TableView中,我原本认为当TableRow实例不再被使用时应该会被垃圾回收(GC),但按照以下步骤操作后出现了不符合预期的现象:

  • 步骤1:打开包含空TableView的窗口,检查内存中没有TableRow实例,符合预期。
  • 步骤2:向TableView填充数据,根据界面空间需求生成了38个TableRow实例。
  • 步骤3:清空TableView中的数据(此时TableView显示占位符信息),多次手动触发GC后检查内存,发现所有38个TableRow实例仍然被VirtualFlow的cells ArrayLinkedList强引用,引用关系如图所示:

内存引用截图

请问这是内存泄漏问题,还是JavaFX设计时就考虑到的预期行为?

以下是可复现该场景的验证代码:

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);
        }
    }
}

内容的提问来源于stack exchange,提问作者Sai Dandem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 04:07:30