You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swing组件更新性能骤降问题:原因与无栈溢出疑问

Swing文本框无限更新与性能问题分析

最小可复现示例(MRE)

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.basic.BasicTextFieldUI;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;

public class TextFieldDemo {

    public static void main(String[] args) {
        Container mainPanel = createMainPanel();
        JFrame frame = new JFrame("Text field demo");
        frame.setContentPane(mainPanel);
        frame.setLocationRelativeTo(null);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    private static Container createMainPanel() {
        JPanel panel = new JPanel();
        panel.add(createTextField());
        return panel;
    }

    private static Component createTextField() {
        JTextField textField = new JTextField(10);
        textField.setUI(new BasicTextFieldUI() {
            int counter = 1;

            @Override
            public void update(Graphics g, JComponent c) {
                super.update(g, c);
                textField.setBorder(textField.getBorder());
//                textField.setBorder(getBorder());
                System.out.printf("UPDATING %d...%n", counter++);
            }

            private Border getBorder() {
                EmptyBorder border = new EmptyBorder(0, 3, 0, 3);
                return border;
            }
        });
        return textField;
    }
}

测试1

直接运行代码并将光标置于文本框内,Swing会无明显原因地持续更新该字段(推测与插入符有关),控制台输出如下:

UPDATING 26...
UPDATING 27...
UPDATING 28...
UPDATING 29...
UPDATING 30...

此时CPU占用正常(截图显示CPU使用率为0),转移焦点后更新停止。

测试2

取消注释以下代码并注释原行:

//                textField.setBorder(textField.getBorder());
                textField.setBorder(getBorder());

此时更新频率大幅提升,控制台计数飞速增长:

UPDATING 39745...
UPDATING 39746...
UPDATING 39747...
UPDATING 39748...

CPU占用显著升高(截图显示CPU使用率达20%+),转移焦点至其他窗口后更新仍不停止,CPU占用进一步升至30%+。

用户推测

在UI的update方法中设置新的Border实例会触发无限循环——setBorder会调用repaint(),进而再次触发update方法,相关源码如下:

// javax.swing.JComponent#setBorder
public void setBorder(Border border) {
    Border         oldBorder = this.border;

    this.border = border;
    firePropertyChange("border", oldBorder, border);
    if (border != oldBorder) {
        if (border == null || oldBorder == null ||
            !(border.getBorderInsets(this).equals(oldBorder.getBorderInsets(this)))) {
            revalidate();
        }
        repaint();
    }
}

疑问

  1. 性能骤降的原因是什么?该猜测是否正确?
  2. 为何不会出现StackOverflowError?

解答

问题1:性能骤降的原因及猜测正确性

你的猜测完全正确。当你在update方法里调用setBorder(getBorder())时,每次都会创建一个新的EmptyBorder实例,传递给setBorder后,方法里会判断border != oldBorder(新实例和旧实例引用不同,条件成立),然后调用repaint()。repaint()会触发Swing的绘制流程,最终又会调用update方法,形成一个无限的事件循环:update → setBorder → repaint → update...

而测试1里调用textField.setBorder(textField.getBorder())时,因为传递的是当前已有的Border实例,setBorder里判断border == oldBorder,不会执行repaint(),所以只有插入符闪烁触发的正常更新,CPU占用自然不高。

问题2:为何不会出现StackOverflowError

因为Swing的绘制事件是在**事件调度线程(EDT)**里通过队列处理的,不是同步递归调用。repaint()并不会立即执行update方法,而是向EDT的事件队列里添加一个绘制请求。当前的update方法执行完毕后,EDT才会取出队列里的下一个绘制请求,调用update。整个过程是队列式的循环,不是方法的嵌套递归,所以不会出现栈溢出。


内容的提问来源于stack exchange,提问作者Sergey Zolotarev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 23:17:03