1

UPDATE: So I've found out what is causing this issue. When my Java window is minimized, my table is rendering every single row. Does anyone know how to prevent this?

I have a JXTable that is constantly updating, deleting, and adding row data every second. We're talking about modifying 10-20 rows every second on average.

Typically, the CPU usage runs between 5% and 10%. It can hit 15% when we're pounding the table with hundreds of updates a second.

However, we've noticed that when our Java window is minimized, whenever ANY update comes through, our CPU usage hits 25% each and every time. We setup a script to add a single row every 5 seconds and when that single row comes through, we're seeing CPU usage hit 25%.

The only explanation I can think of is the use of SwingUtilities.invokeAndWait(). I'm modifying the row data in a background thread and using invokeAndWait for the various fireTableDataChanged() methods.

I use invokeAndWait because I need to fire off my events in order. E.g, I delete some rows, call fireTableRowsDeleted(), then I add some rows and call fireTableRowsInserted().

Any ideas why my CPU usage hits 25% ONLY when table updates and my window is minimized?

jdb1015
  • 127
  • 6

3 Answers3

2

JTable uses the flyweight pattern to render only visible cells, but the host OS may by interfering, e.g. by trying to animate the minimized icon; profile to be sure. Whatever the cause, publish() updates destined for the TableModel in the doInBackground() implementation of a SwingWorker, as shown here. The worker will coalesce the updates and deliver them to process() at a sustainable rate, ~30 Hz. Your TableModel can then defer firing an update event in a way that makes sense for your application.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
2

I use invokeAndWait because I need to fire off my events in order

There is no need to use invokeAndWait(). You can just use invokeLater(). The events will still be executed in order they are received.

the various fireTableDataChanged() methods.

You should not be invoking fireTableDataChanged. The TableModel will invoke the appropriate fireXXX() method. Why repaint the whole table when you may only change a few rows. The RepaintManager will consolidate multiple paint requests into one if necessary.

Edit:

Here is some code I had lying around. All updates are done to the model on the EDT and the code does not invoke the fireXXX(...) methods:

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;

public class TableThread extends JFrame
    implements ActionListener, Runnable
{
    JTable table;
    DefaultTableModel model;
    int count;

    public TableThread()
    {
        String[] columnNames = {"Date", "String", "Integer", "Decimal", "Boolean"};
        Object[][] data =
        {
            {new Date(), "A", new Integer(1), new Double(5.1), new Boolean(true)},
            {new Date(), "B", new Integer(2), new Double(6.2), new Boolean(false)},
            {new Date(), "C", new Integer(3), new Double(7.3), new Boolean(true)},
            {new Date(), "D", new Integer(4), new Double(8.4), new Boolean(false)}
        };

        model = new DefaultTableModel(data, columnNames);
        table = new JTable( model );
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        table.setIgnoreRepaint(false);

        JScrollPane scrollPane = new JScrollPane( table );
        getContentPane().add( scrollPane );

        JButton button = new JButton( "Start Thread to Update Table" );
        button.addActionListener( this );
        getContentPane().add(button, BorderLayout.SOUTH );
    }

    public void actionPerformed(ActionEvent e)
    {
        new Thread( this ).start();
        table.requestFocus();
    }

    public void run()
    {
        Random random = new Random();

        while (true)
        {
            final int row = random.nextInt( table.getRowCount() );

            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    table.setValueAt( new Integer(count++), row, 2);
                    table.setRowSelectionInterval(row, row);
                    Object[] aRow = { new Date(), "f", row, new Double(123), new Boolean(true) };
                    model.addRow( aRow );
                }
            });

            try { Thread.sleep(500); }
            catch(Exception e) {}
        }
    }

    public static void main(String[] args)
    {
        TableThread frame = new TableThread();
        frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
        frame.pack();
        frame.setVisible(true);
    }
}

The CPU is consistent whether the frame is visible or minimized.

If you need more help then post a proper SSCCE like the code above to demonstrate the problem.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • I'm doing all these updates sequentially in one method. If you delete items from your model, fire off events in invokeLater(), then add items and fire off another event in invokeLater(), your model will be out of sync by the time your events actually get fired in a invokeLater. That's why I have to use invokeAndWait(). E.g. `rows.remove1(); fireTableRowsDeleted(1), rows.add(something); fireTableRowsInserted(lastIndex)j. And by various fireTableDataChanged methods, I mean fireTableRowsDeleted(), fireTableRowsInserted(), etc. I never call fireTableDataChanged(). – jdb1015 Nov 21 '15 at 02:12
  • @jdb1015 You shouldn't be modifying the data that the table model relies on outside of the EDT, you should be modifying the data AND firing the events from within the context of the EDT, the reason is you risk a race condition between your thread and the table/model/EDT doing it the other way – MadProgrammer Nov 21 '15 at 02:22
  • @jdb1015, You completely missed my point about NOT invoking the fireXXX() methods manualy. That is the job of the TableModel. `I'm doing all these updates sequentially in one method.` - The easiest way is to wrap the entire method in an invokeLater() to make sure all the code executes on the EDT. So you just invoke model.remove(), model.add() and all the code will execute sequentially on the EDT and all updates will be done in the proper order. You are complicating the logic. See edit. – camickr Nov 21 '15 at 02:53
  • So this isn't your standard table; I'm modifying tables with 2,000 rows and at some points I'm deleting, updating, and adding hundreds of rows per second for up to 15 (sometimes more) tables at once. Doing a hundred deletes alone one at a time has about a 10 second freeze. Doing this all on the AWT thread absolutely kills performance. On top of that, we've gotten performance in these situations down to <10%. I'm really looking for why, when the GUI is minimized, performance jumps to 25% when even just 1 row is added to the table. – jdb1015 Nov 21 '15 at 17:46
  • @jdb1015, well I've never seen the problem before so I have to history to rely on. I can't guess what you are doing based on a verbal description. Without a `SSCCE` to demonstrate the problem I can't make any more suggestions than I already have. Good luck. – camickr Nov 21 '15 at 21:30
  • appreciate the help! may do that if I can't figure this out soon. – jdb1015 Nov 23 '15 at 00:55
2

In JXTable, this method is called whenever the model changes:

protected void postprocessModelChange(TableModelEvent e) {
    if (forceRevalidate && filteredRowCountChanged) {
        resizeAndRepaint();
    }
    filteredRowCountChanged = false;
    forceRevalidate = false;
}

The resizeAndRepaint() call was what appears to be forcing every single row to be repainted when the window is minimized. Overriding as below seems to fix the issue:

@Override
protected void resizeAndRepaint()
{
    JFrame window = (JFrame)  SwingUtilities.getAncestorOfClass(JFrame.class, this);
    if(window != null && window.getState() != JFrame.ICONIFIED)
    {
        super.resizeAndRepaint();
    }
}
jdb1015
  • 127
  • 6