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
相关产品推荐
相关产品推荐

