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