14

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

Redhat 6u7

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

Ubuntu 16.04 on Intel 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?
Adam
  • 35,919
  • 9
  • 100
  • 137
  • if you're going to add it sequentially you can do it without an executor service. And you don't need a synchronized block, because the list itself is already synchronized – Merve Sahin Jul 04 '16 at 16:49
  • I've tried it with CentOS 6.8 x86 (latest updates installed) and with JDK 8u40 x86 from Oracle (in a virtualbox). I had no problems with sluggish other apps. But for your example my heapsize is max 45mb and not like yours over 100mb max. – aw-think Jul 05 '16 at 09:06
  • Is your "metal" for Windows and Ubuntu 16.04 the same? What graphics card do you have and with what driver installed, especially on linux? – aw-think Jul 05 '16 at 09:12
  • Failing case on Ubuntu is on a "a01:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] RV530/M56-P [Mobility Radeon X1600]" with standard radeon driver. I'll the details for the Redhat machine tomorrow (it's at the office) – Adam Jul 05 '16 at 19:34
  • There is a "similar" bug with java, ati and X, but it was in a very old release of Ubuntu so I think this isn't valid anymore, but could be a good starting point. Maybe you can try the same setup on another hardware with intel or nvidia graphics?https://bugs.launchpad.net/ubuntu/+source/sun-java6/+bug/250931 – aw-think Jul 06 '16 at 10:12
  • Just a few questions, as i can see you increment counter and "sometimes" show results in table. What's the main goal? Add items as fast you can? But when should we refresh table? – Vladislav Kysliy Dec 18 '16 at 13:32
  • I can't reproduce :( BTW: `itemsToAdd.add(thing)` needs to be synchronized (eg: you don't want another thread to append to it while you are reversing the list); access to the data list must be synchronized too (eg: you don't want another thread to modify data while you are in the if (data.size() > maxSize) ). – giorgiga Sep 15 '17 at 19:57
  • @giorgiga Thanks for looking at this. Note itemsToAdd.add() is already synchronized on itself, because it is created using Collections.synchronizedList(). This standard JDK utility method creates synchronized wrappers which synchronize all the core methods, including add()... – Adam Sep 18 '17 at 11:07
  • @Adam Lists from Collections.synchronizedList do not sync on themselves: they synch on a internal "mutex" and so itemsToAdd.add() can be executed (eg) after Collections.reverse() and before data.addAll() – giorgiga Sep 18 '17 at 13:49
  • @giorgiga javafx.collections.FXCollections works as you describe, however java.util.Collections does not. It uses the mutex of itself, with an additional constructor allowing to pass in your own mutex. The example in https://docs.oracle.com/javase/tutorial/collections/implementations/wrapper.html relies upon this. If you don't believe me look at the source code.... http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/util/Collections.java#Collections.SynchronizedCollection Line 2006 "mutex = this;" – Adam Sep 18 '17 at 14:28
  • @Adam didn't know that - thanks – giorgiga Sep 18 '17 at 18:00
  • You're not running Scenic View are you? I had a very similar issue when running a complicated GUI with Scenic View loaded. – ScarySpider Jan 02 '18 at 19:59

1 Answers1

2

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

Grumblesaurus
  • 3,021
  • 3
  • 31
  • 61
  • That was very helpful. Thank you! For those using maven, add this command line inside plugin>configuration: -XX:+UseG1GC If you want it running directly from intellij, just add "-XX:+UseG1GC" in VM arguments inside Run/Debug Configurations – FARS May 28 '22 at 06:55