SortedList hanging on to references & preventing GC

Kevin Rushforth kevin.rushforth at oracle.com
Thu Dec 4 19:52:55 UTC 2025


Since there is already a bug tracking this, can you add the additional 
information and test case to the bug report?

-- Kevin


On 12/4/2025 11:44 AM, Andy Goryachev wrote:
> 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/dd179ff5/attachment-0001.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: sorted_list_mem.gif
Type: image/gif
Size: 128014 bytes
Desc: not available
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20251204/dd179ff5/sorted_list_mem-0001.gif>


More information about the openjfx-dev mailing list