SortedList hanging on to references & preventing GC

Andy Goryachev andy.goryachev at oracle.com
Thu Dec 4 19:44:32 UTC 2025


Spoke too soon: we already have
https://bugs.openjdk.org/browse/JDK-8184166
from 2017.

Question: are there any more places where we don't null the unused entries?

-andy

From: openjfx-dev <openjfx-dev-retn at openjdk.org> on behalf of Andy Goryachev <andy.goryachev at oracle.com>
Date: Thursday, December 4, 2025 at 11:40
To: John Hendrikx <john.hendrikx at gmail.com>, openjfx-dev at openjdk.org <openjfx-dev at openjdk.org>
Subject: Re: SortedList hanging on to references & preventing GC

> Time pressure and insufficient testing.

Alas!  I can create a ticket, unless someone has already created one.

-andy


From: openjfx-dev <openjfx-dev-retn at openjdk.org> on behalf of John Hendrikx <john.hendrikx at gmail.com>
Date: Thursday, December 4, 2025 at 11:18
To: openjfx-dev at openjdk.org <openjfx-dev at openjdk.org>
Subject: Re: SortedList hanging on to references & preventing GC


This looks like a classic problem where unused elements in an array are not nulled.  Looking at the code that updates the `size` field, there are a few code paths that are not setting unused elements to null.  I also saw no code that ever shrinks the arrays.  So it looks this implementation is only half finished.

So, yes, I'd say this is a bug.

I'm going to guess there's a very good reason for this behaviour.
Time pressure and insufficient testing.

--John

On 04/12/2025 19:40, Cormac Redmond wrote:
Hi,

I've traced a memory issue back to a SortedList (surprisingly), where it's hanging on to objects that could/should have been GC'd.

SortedList's internal arrays will only grow (but not shrink) in line with the source ObservableList (I'm sure there are reasons for this). So even when your source ObservableList shrinks, SortedList is hanging on to references to objects the source list once contained, even though they're completely removed from the source list.

This leads to a substantial waste of memory, especially when just one momentarily large dataset leads to a permanent spike in unnecessary memory usage for the remainder for the application's lifetime. I'm sure I'm not the first to raise this or ask about it & I'm going to guess there's a very good reason for this behaviour. But could someone explain this? I would have thought SortedList should/could remain as lean as the source list. FilteredList is somewhat similar, except it stores an array of ints (indexes), so less of a memory hit, but still pointless nonetheless.

Example GIF + code below: setup a typical sortable table (so, ObservableList + FilteredList + SortableList), where the print button shows that when you add a lot of data, and remove it, SortedList retains references to all of the old objects. Some reflection is used to print that information.

[sorted_list_mem.gif]


Code to reproduce:

public class SortedListMemWasteDemo extends Application {

    record PotentialLargeData(String info) {}

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        ObservableList<PotentialLargeData> masterData = FXCollections.observableArrayList(
                new PotentialLargeData("Initial info 1"),
                new PotentialLargeData("Initial info 2"));

        TableColumn<PotentialLargeData, String> col = new TableColumn<>("Info");
        col.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().info()));

        TableView<PotentialLargeData> table = new TableView<>();
        table.getColumns().add(col);

        FilteredList<PotentialLargeData> filteredData = new FilteredList<>(masterData, item -> true);
        SortedList<PotentialLargeData> sortedData = new SortedList<>(filteredData);
        sortedData.comparatorProperty().bind(table.comparatorProperty());
        table.setItems(sortedData);

        Button loadManyBtn = new Button("Add 10,000 items");
        loadManyBtn.setOnAction(e -> {
            System.out.println("Adding 10000 items to master data");
            masterData.clear();
            for (int i = 0; i < 10000; i++) masterData.add(new PotentialLargeData("Info item " + i));
        });

        Button reduceBtn = new Button("Reduce to 2");
        reduceBtn.setOnAction(e -> {
            System.out.println("Reducing master data size to 2");
            masterData.setAll(new PotentialLargeData("New info 1"), new PotentialLargeData("New info 2"));
        });

        Button printBtn = new Button("Print interal sizes");
        printBtn.setOnAction(e -> {
            try {
                System.out.println("Items the user sees: " + sortedData.size());
                int[] filtered = (int[]) getFieldValue(filteredData, "filtered");
                System.out.println("FilteredList.filtered length: " + filtered.length);
                // This is a hidden and significant waste of memory
                Object[] sorted = (Object[]) getFieldValue(sortedData, "sorted");
                System.out.println("SortedList.sorted length: " + sorted.length);
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        });

        primaryStage.setScene(new Scene(new VBox(table, loadManyBtn, reduceBtn, printBtn), 600, 400));
        primaryStage.show();
    }

    private static Object getFieldValue(Object object, String fieldName) throws Exception {
        Field field = object.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(object);
    }
}


Kind Regards,
Cormac
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20251204/0c77b141/attachment-0001.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: sorted_list_mem.gif
Type: image/gif
Size: 128014 bytes
Desc: sorted_list_mem.gif
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20251204/0c77b141/sorted_list_mem-0001.gif>


More information about the openjfx-dev mailing list