JavaFX scrolling table update performance degrades

2020-08-10 07:50发布

问题:

I have a TableView that shows last N items, new items at top, remove items from the bottom etc... What appears to be happening is CPU load increases over time to point where other X applications on same machine become sluggish.

Platform details: Redhat 6.7, 32 bit, Java 1.8u40

Things I've tried

  • Introduced runLater() - original code updated the observable list from a non-FX thread - apparently this is wrong
  • Optimize - only place new Runnables on JavaFX application thread if there isn't already an update in progress
  • Optimize -bulk updates to the Observable list rather than individual adds
  • used jvisual VM to indentify any memory leaks, couldn't find anything.
  • I've tried to recreate this
    • Windows 7 (on metal) - JDK 8u40 64 bit => does not occur
    • Ubuntu 16.04 JDK 8u40 64 bit (inside a VM with vmwgfx) => does not occur
    • Ubuntu 16.04 OpenJDK + OpenJFX latest (8u91) (on metal) => does occur

JVisual VM - Redhat 6u7 (32bit) on newish hardware

JVisual VM - Ubuntu 16.04 (64bit) on old hardware (2008 iMac)

This issue was part of larger app, but I've isolated it as a smaller example below. This makes other apps sluggish after a couple of minutes, but only on the Redhat 6u7 platform.

public class TableUpdater extends Application {
    private int maxItems = 30;
    private AtomicBoolean pending = new AtomicBoolean();
    public class Thing {
        private String foo;
        public Thing(String foo) {
            this.foo = foo;
        }
        public String getFoo() {
            return foo;
        }
        @Override
        public int hashCode() {
            return foo.hashCode();
        }
        @Override
        public boolean equals(Object obj) {
            if (! (obj instanceof Thing)) {
                return false;
            }
            return ((Thing)obj).foo.equals(foo);
        }
    }

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

    private int counter = 0;
    private ObservableList<Thing> data;
    private List<Thing> itemsToAdd = Collections.synchronizedList(new ArrayList<Thing>());

    @Override
    public void start(Stage primaryStage) throws Exception {
        TableView<Thing> table = new TableView<>();
        data = FXCollections.observableArrayList();
        table.setItems(data);

        TableColumn<Thing, String> fooCol = new TableColumn<>("Foo");
        fooCol.setCellValueFactory(new PropertyValueFactory<Thing, String>("foo"));
        table.getColumns().addAll(fooCol);

        Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> {
            add(new Thing(String.format("%08d", counter++)));
        }, 0, 2, TimeUnit.MILLISECONDS);

        primaryStage.setScene(new Scene(table));
        primaryStage.setWidth(400);
        primaryStage.setHeight(400);
        primaryStage.show();
    }

    private void add(Thing thing) {
        itemsToAdd.add(thing);

        if (!pending.getAndSet(true)) {
            Platform.runLater(() -> {
                synchronized (itemsToAdd) {
                    Collections.reverse(itemsToAdd);
                    data.addAll(0, itemsToAdd);
                    itemsToAdd.clear();
                }
                if (data.size() > maxItems) {
                    data.remove(maxItems, data.size());
                }
                pending.set(false);
            });
        }
    }
}

Questions

  • Is this issue something to do with way I'm updating the table or an underlying bug?
  • Any more efficient ways to update the table?

回答1:

Try using the G1GC garbage collector. I had a similar, but not identical, issue (large number of persistent objects in tableview) that was resolved by using G1GC.

java XX:+UseG1GC