Java 8u161/8u162 позволяет использовать приложение Swing для использования процессора

При запуске приложения Swing на 8u161 или 8u162 и фокусе находится в JTextField, и вы переключаетесь на другое приложение (например, Chrome) и обратно в приложение. Использование процессора увеличивается до 15% на моем 8-ядерном ПК с Windows 10 (как если бы весь ядро занятые обработки событий).

Просто запустите приложение и переключите его пару раз. Если я нажму на вкладку на панели с вкладками, загрузка процессора упадет до 0, как ожидалось.

public class Test {
  public static void main(String... args) {
    SwingUtilities.invokeLater(() -> {
      JFrame f = new JFrame("Test");
      JTabbedPane tp = new JTabbedPane();
      tp.addTab("tab 1", new JTextField(20));
      f.add(tp);
      f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      f.pack();
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

Я попытался посмотреть очередь событий, чтобы увидеть, что происходит, и это очень похоже на то, что последнее событие обрабатывается снова и снова

Если я добавлю это в вышеуказанную программу, я получаю много java.awt.event.InvocationEvent [INVOCATION_DEFAULT, runnable = sun.awt.windows.WInputMethod...

Toolkit.getDefaultToolkit().getSystemEventQueue().push(new EventQueue(){
  @Override protected void dispatchEvent(AWTEvent event) {
    System.out.println(event);
    super.dispatchEvent(event);
  }
});

Работает нормально на 8u151, 8u152 и 9.0.4

У меня много клиентов, которые обновляют до 161 и получают эту проблему, поэтому любые предложения об обходном пути очень ценятся. Я подал ошибку с Oracle

JProfiler показывает это: enter image description here

Кажется, работает нормально на 8u172 b02

Согласно openjdk это было введено 8184016 и зафиксировано 8183504

Ответы

Ответ 1

Используют ли ваши клиенты методы ввода? Если вам не нужны методы ввода, я предлагаю вам отключить его.

public class Test {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
              JFrame f = new JFrame("Test");
              JTabbedPane tp = new JTabbedPane();
              JTextField tf = new JTextField();
              tf.enableInputMethods(false); // disable IM
              tp.addTab("tab 1", tf);
              f.add(tp);
              f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              f.pack();
              f.setLocationRelativeTo(null);
              f.setVisible(true);
            }
        });
    }
}

Ответ 2

Это, вероятно, довольно уродливо, но это обходной путь, который я создал на основе моих наблюдений, когда возникает ошибка. Я уверен, что можно создать лучшее обходное решение, чтобы лучше понять, почему именно ошибка возникает в первую очередь.

Это существенно предотвращает JTextComponent как первое, что после фокусировки окна, перенос фокуса на другой компонент (upFocusCycle(component) для меня помещается в рамку/диалог), а затем передает его обратно в JTextComponent. Я не знаю, будет ли это работать в каждом случае или даже что-то сломать, но, похоже, это работает для меня. Разумеется, используйте на свой страх и риск.

public class JTextFieldTest {

    public static final void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            // Install workaround, without this the bug occurs
            installTextComponentFocusWorkaround();

            JFrame window = new JFrame("Test");
            window.setLocationByPlatform(true);
            window.add(new JButton("Button"), BorderLayout.CENTER);
            window.add(new JTextField(), BorderLayout.SOUTH);
            window.pack();
            window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            window.setVisible(true);
        });
    }

    public static void installTextComponentFocusWorkaround() {
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addVetoableChangeListener(new VetoableChangeListener() {

            private boolean rejectNext = false;
            private JComponent target;

            @Override
            public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
                if (evt.getNewValue() != null) {
                    if (evt.getPropertyName().equals("focusOwner")) {
                        if (evt.getNewValue() instanceof JTextComponent) {
                            if (rejectNext) {
                                JComponent component = (JComponent) evt.getNewValue();
                                KeyboardFocusManager.getCurrentKeyboardFocusManager().upFocusCycle(component);
                                target = component;
                                System.out.println("Rejected JTextComponent focus");
                                throw new PropertyVetoException("Rejected JTextComponent focus", evt);
                            }
                        } else {
                            rejectNext = false;
                            if (target != null) {
                                System.out.println("Temp focus: " + evt.getNewValue());
                                target.requestFocus();
                                target = null;
                            }
                        }
                    } else if (evt.getPropertyName().equals("focusedWindow")) {
                        System.out.println("Window focused");
                        rejectNext = true;
                    }
                }
            }
        });
    }

}

Я также пробовал такие вещи, как очистка фокуса вообще или только бросание PropertyVetoException, но только на самом деле фокусировался на других, прежде чем JTextComponent похоже, работал.

Я ищу JTextComponent потому что ошибка произошла для меня как с JTextField и с JTextArea, хотя я не уверен, что затронуты и другие классы.