Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8237926

Potential memory leak of model data in javafx.scene.control.ListView

    Details

    • Subcomponent:
    • CPU:
      x86_64
    • OS:
      generic

      Description

      A DESCRIPTION OF THE PROBLEM :
      The selection model of ListView keeps the most recently changed items of the underlying ObservableList as strong reference in "itemsListChange" of SelectedItemsReadOnlyObservableList.
      The objects cannot be garbage collected even if they are removed from the list.

      Seems related to JDK-8227619 but the symptom is different - it is not the ListView that is expected to be freed in this scenario but the application's model objects.
      (Test case adapted from JDK-8227619)

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Create a ListView to an ObservableList of potentially big items, DON't select anything, empty the list.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The objects are not referenced any more and can be freed.
      ACTUAL -
      The objects are held by SelectedItemsReadOnlyObservableList.itemsListChange and will not be GC'ed.

      ---------- BEGIN SOURCE ----------
      package listview;

      import static org.junit.Assert.fail;

      import java.lang.ref.WeakReference;
      import java.util.concurrent.CountDownLatch;
      import java.util.concurrent.TimeUnit;

      import org.junit.Assert;
      import org.junit.Before;
      import org.junit.BeforeClass;
      import org.junit.Test;

      import javafx.application.Application;
      import javafx.application.Platform;
      import javafx.collections.FXCollections;
      import javafx.collections.ObservableList;
      import javafx.scene.control.ListView;
      import javafx.stage.Stage;

      public class ListViewTest {

          static private class ContentObject {

          }

          static CountDownLatch startupLatch;

          private ListView<ContentObject> listView;

          private WeakReference<ContentObject> objectRef;

          public static class TestApp extends Application {

              @Override
              public void start(Stage stage) throws Exception {
                  startupLatch.countDown();
              }
          }

          private static ObservableList<ContentObject> items = FXCollections.observableArrayList();

          public ListViewTest() {

          }

          @BeforeClass
          public static void initJavaFX() {
              startupLatch = new CountDownLatch(1);
              new Thread(() -> Application.launch(TestApp.class, (String[]) null)).start();
              try {
                  if (!startupLatch.await(15, TimeUnit.SECONDS)) {
                      fail("Timeout waiting for FX runtime to start");
                  }
              } catch (InterruptedException ex) {
                  fail("Unexpected exception: " + ex);
              }
          }

          @Before
          public void initListView() throws Exception {
              CountDownLatch latch = new CountDownLatch(1);
              ContentObject contentObject = new ContentObject();
              items.add(contentObject);
              objectRef = new WeakReference<>(contentObject);

              Platform.runLater(() -> {
                  listView = new ListView<>(items);
                  latch.countDown();
              });

              if (!latch.await(15, TimeUnit.SECONDS)) {
                  fail("Timeout waiting for FX listview initialization");
              }
          }

          @Test
          public void testGarbageCollect() throws InterruptedException {
              CountDownLatch latch = new CountDownLatch(1);
              Platform.runLater(() -> {
                  items.clear();
                  latch.countDown();
              });

              if (!latch.await(15, TimeUnit.SECONDS)) {
                  fail("Timeout clear items");
              }

              for (int i = 0; i < 10; i++) {
                  System.gc();
                  System.runFinalization();

                  if (objectRef.get() == null) {
                      break;
                  }

                  try {
                      System.out.println("Waiting");
                      Thread.sleep(500);
                  } catch (Exception ex) {
                  }
              }
              Assert.assertTrue("Could not garbage collect content object", objectRef.get() == null);
              // Object is held by com.sun.javafx.scene.control.SelectedItemsReadOnlyObservableList.itemsListChange - added by
              // a list content change but when no selection index change occurs and it is not nulled in line #101
          }

          @Test
          public void testGarbageCollectOkWhenSelectionOccured() throws InterruptedException {
              CountDownLatch latch = new CountDownLatch(1);

              Platform.runLater(() -> {
                  listView.getSelectionModel().selectFirst();
                  latch.countDown();
              });
              if (!latch.await(15, TimeUnit.SECONDS)) {
                  fail("Timeout select item");
              }

              Platform.runLater(() -> {
                  items.clear();
                  latch.countDown();
              });

              if (!latch.await(15, TimeUnit.SECONDS)) {
                  fail("Timeout clear items");
              }

              for (int i = 0; i < 10; i++) {
                  System.gc();
                  System.runFinalization();

                  if (objectRef.get() == null) {
                      break;
                  }

                  try {
                      System.out.println("Waiting");
                      Thread.sleep(500);
                  } catch (Exception ex) {
                  }
              }
              Assert.assertTrue("Could not garbage collect content object", objectRef.get() == null);
          }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Have an item selected before clearing the list, this causes the selection index to change and the internally stored change to be cleaned up.

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                arapte Ambarish Rapte
                Reporter:
                webbuggrp Webbug Group
              • Votes:
                0 Vote for this issue
                Watchers:
                6 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: