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

Java Swing组件运行多日后冻结问题排查求助

Swing GUI长期运行后冻结问题排查

环境与场景

  • 部署在虚拟机(VM)上的7×24小时后台工具,通过普通java.lang.Thread工作线程执行任务,调用外部Python进程处理业务
  • 配套Swing GUI用于展示任务进度、异常信息,所有GUI更新通过appendTextToGUI方法提交到EDT线程执行

问题现象

  • 运行数日后GUI完全冻结:无法更新内容,滚动条、窗口按钮无响应,调整窗口大小时新区域黑屏
  • 后台工作线程仍正常运行,工作日志正常记录
  • 连接JVM调试器后,调用任何组件方法(如.getName())都会导致调试器永久挂起

排查细节

  • 所有GUI操作均通过appendTextToGUI方法,使用SwingUtilities.invokeLater()提交到EDT执行
  • 工作线程为普通Thread,而非SwingWorker,通过ProcessBuilder调用Python进程并将输出传入GUI更新方法
  • 短时间压力测试(持续发送GUI更新请求)无法复现冻结问题

相关代码

ProgressGUI核心代码

public class ProgressGUI extends JFrame {
    private static ProgressGUI instance;
    static JTextPane textpane;
    private StyleContext context;
    private String previousMessage;

    public static ProgressGUI getProgressPanel() {
        if (instance == null) {
            instance = new ProgressGUI();
        }
        return instance;
    }

    private ProgressGUI() {
        LimitedSizeDocument document = new LimitedSizeDocument();
        textpane = new JTextPane(document);
        textpane.setFont(textpane.getFont().deriveFont(24f));
        textpane.setEditable(false);
        textpane.setOpaque(false);
        context = new StyleContext();

        this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                int result = JOptionPane.showConfirmDialog(instance, "Exit request. Are you sure you want to close this worker?", "Confirm Exit", JOptionPane.YES_NO_OPTION);
                if (result == JOptionPane.YES_OPTION) {
                    if(MailboxProcessor.isProcessorRunning()) {
                        MailboxProcessor.requestShutdown();
                    }
                    else{
                        System.exit(0);
                    }
                }
            }
        });
        this.getContentPane().add(getContentPanel(textpane));
        this.pack();
        this.setSize(new Dimension(1400, 800));
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    private Style getTextStyle(Color color) {
        if (context.getStyle(color.toString()) == null) {
            // build a style and set some style properties
            Style newStyle = context.addStyle(color.toString(), null);
            StyleConstants.setForeground(newStyle, color);
            return newStyle;
        }
        return context.getStyle(color.toString());
    }

    private JPanel getContentPanel(JTextComponent textComponent) {
        JPanel jPanel = new JPanel();
        jPanel.setLayout(new BorderLayout());

        Image backGroundImage = new ImageIcon(FolderStructure.CONFIG_FOLDER + "inter.png").getImage();

        JViewport viewport = new JViewport() {
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g); //initialize the default behaviour of the graphics
                g.setColor(Color.WHITE); //change the color of the background of the graphic context
                g.fillRect(0, 0, getWidth(), getHeight());

                // Paint image
                if (backGroundImage != null) {
                    Graphics2D g2d = (Graphics2D) g.create(); //create copy of the original graphic (that would be the bare background and TextPane)
                    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); //any new graphic that is now added (our image) is painted with SRC_OVER on top of the original graphic, and with transparency 0.5f
                    g2d.drawImage(backGroundImage, 0, 0, getWidth(), getHeight(), this);
                    g2d.dispose();
                }
            }
        };


        JScrollPane jScrollPane = new JScrollPane(textComponent, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        jScrollPane.setViewport(viewport);
        jScrollPane.setViewportView(textComponent);

        //add AdjustmentListener to implement autoscrolling when the scrollbar is at the bottom and not getting adjusted by the user
        jScrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
            BoundedRangeModel brm = jScrollPane.getVerticalScrollBar().getModel();
            boolean wasAtBottom = true;

            @Override
            public void adjustmentValueChanged(AdjustmentEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        if (!brm.getValueIsAdjusting()) {
                            if (wasAtBottom) {
                                brm.setValue(brm.getMaximum());
                            }
                        } else {
                            wasAtBottom = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
                        }
                    }
                });
            }
        });

        jPanel.add(jScrollPane);
        return jPanel;
    }

    public void appendTextToGUI(String str) {
        appendTextToGUI(str, Color.BLACK);
    }

    public void appendTextToGUI(String str, Color color) {
        if (str.equals(previousMessage)) return; //prevent spam when checking for new emails many times
        previousMessage = str;
        SwingUtilities.invokeLater(new Runnable() { //GUI modification need to run on EDT
            @Override
            public void run() {
                try {
                    Document document = textpane.getDocument();
                    document.insertString(document.getLength(), str + System.lineSeparator(), getTextStyle(color));
                    ProgressGUI.this.revalidate();
                    ProgressGUI.this.repaint();
                } catch (BadLocationException e) {
                    Provider.getLogger().info(e.getMessage() + " for string " + str);
                }
            }
        });
    }

    /**
     * Limited size Document class was created to keep the size of the Text Pane of the constantly running workers in check
     */
    private class LimitedSizeDocument extends DefaultStyledDocument {
        private int MAX_DOC_SIZE = 100000;
        @Override
        public void insertString(int offs, String additionalString, AttributeSet a) throws BadLocationException {
            // Calculate the number of characters that will exceed the maximum size
            int excess = (getLength() + additionalString.length()) - MAX_DOC_SIZE;
            if (excess > 0) {
                // If the inserted text exceeds the maximum size, discard the oldest characters
                int removeCount = Math.min(offs, excess);
                super.remove(0, removeCount);
                offs = offs - removeCount;
            }
            // Insert the new text
            super.insertString(offs, additionalString, a);
        }
    }
}

压力测试代码

@Ignore
@Test
public void testSTRESSProgressGUI() throws BadLocationException, InterruptedException, InvocationTargetException {
    ProgressGUI progressGUI = ProgressGUI.getProgressPanel();
    progressGUI.setVisible(true);

    String str1 = "progressing...";
    String str2 = "finished";
    while(true){
        progressGUI.appendTextToGUI(str1 + str2 + new Random().nextInt(((int) Instant.now().getEpochSecond()))+ str1+str1+str1+str1+str1+str1+str1 + str2 + new Random().nextInt(((int) Instant.now().getEpochSecond()))+ str1+str1+str1+str1+str1+str1+str1 + str2 +str1+str1+new Random().nextInt(((int) Instant.now().getEpochSecond()))+str1+str1+str1+str1);
        progressGUI.appendTextToGUI("\uD83E\uDD91 Stalking for new emails... \uD83E\uDD91 ", new Color(0, 185, 0));
        progressGUI.appendTextToGUI("\uD83C\uDF89OCR batch finished!\uD83C\uDF89" + System.lineSeparator(), Color.MAGENTA);
        Thread.sleep(50);
    }
}

疑问

虚拟机大多时间无显示屏幕(仅远程连接时显示),是否会导致Swing组件出现此类冻结问题?


内容的提问来源于stack exchange,提问作者Theo S

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 13:47:32