如何防止 JTextPane.setCaretPosition(int) 中的内存泄漏? [英] How to prevent memory leak in JTextPane.setCaretPosition(int)?

查看:23
本文介绍了如何防止 JTextPane.setCaretPosition(int) 中的内存泄漏?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用基于 Swing 的 GUI 开发 Java 应用程序.该应用程序使用 JTextPane 输出日志消息,如下所示: 1) 截断现有文本以保持总文本大小低于限制;2) 添加新文本;3)滚动到最后(实际逻辑略有不同,但这里无关紧要).

I'm working on a Java application with Swing-based GUI. The application uses JTextPane to output log messages as follows: 1) truncate existing text to keep total text size under the limit; 2) append new text; 3) scroll to the end (actual logic is slightly different, but it's irrelevant here).

我使用 Eclipse 和 JVM Monitor 来确定合理的文本大小限制并发现显着的内存泄漏.我试图从底层文档中删除 UndoableEditListener 并禁用自动插入符号位置更新(通过使用 DefaultCaret.NEVER_UPDATEJTextPane.setCaretPosition(int) 显式更改位置),但没有成功.最后,我决定完全禁用改变插入符号位置,这修复了泄漏.

I was using Eclipse with JVM Monitor to determine reasonable text size limit and found significant memory leak. I tried to remove UndoableEditListeners from the underlying document and disable automatic caret position updates (by changing the position explicitly with DefaultCaret.NEVER_UPDATE and JTextPane.setCaretPosition(int)), but without success. Finally, I decided to disable changing caret position completely, and this fixed the leak.

我有两个问题:

  1. 我的代码有问题吗?如果是,我该如何更改它以完成任务?

  1. Is there an issue with my code? If yes, how can I change it to accomplish the task?

这是 Swing/JVM 错误吗?如果是,我该如何举报?

Is it a Swing/JVM bug? If yes, how can I report it?

详情:

这是 SSCCE:带有 textPane 和两个按钮的 GUI,用于小型和压力测试.FIXFIXXX 标志对应于我修复内存泄漏的尝试.

Details:

Here is SSCCE: GUI with textPane and two buttons, for small and stress tests. FIX and FIXXX flags correspond to my attempts to fix memory leak.

package memleak;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.*;

class TestMain
{
  private JTextPane textPane;
  // try to fix memory leak
  private static final boolean FIX = false;
  // disable caret updates completely
  private static final boolean FIXXX = false;
  // number of strings to append
  private static final int ITER_SMALL = 20;
  private static final int ITER_HUGE = 1000000;
  // limit textPane content
  private static final int TEXT_SIZE_MAX = 100;

  TestMain()
  {
    JFrame frame = new JFrame();
    JPanel panel = new JPanel();
    textPane = new JTextPane();
    textPane.setEditable(false);
    if (FIX)
    {
      tryToFixMemory();
    } // end if FIX
    JScrollPane scrollPane = new JScrollPane(textPane);
    scrollPane.setPreferredSize(new Dimension(100, 100) );
    panel.add(scrollPane);
    JButton buttonSmall = new JButton("small test");
    buttonSmall.addActionListener(new ButtonHandler(ITER_SMALL) );
    panel.add(buttonSmall);
    JButton buttonStress = new JButton("stress test");
    buttonStress.addActionListener(new ButtonHandler(ITER_HUGE) );
    panel.add(buttonStress);
    frame.add(panel);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.pack();
    frame.setVisible(true);
  } // end constructor

  public static void main(String[] args)
  {
    @SuppressWarnings("unused")
    TestMain testMain = new TestMain();
  } // end main

  private void append(String s)
  {
    Document doc = textPane.getDocument();
    try
    {
      int extraLength = doc.getLength() + s.length() - TEXT_SIZE_MAX;
      if (extraLength > 0)
      {
        doc.remove(0, extraLength);
      } // end if extraLength
      doc.insertString(doc.getLength(), s, null);
      if (FIX && !FIXXX)
      {  // MEMORY LEAK HERE
        textPane.setCaretPosition(doc.getLength() );
      } // end if FIX
    }
    catch (Exception e)
    {
      e.printStackTrace();
      System.exit(1);
    } // end try
  } // end method append

  private void tryToFixMemory()
  {    
    // disable caret updates
    Caret caret = textPane.getCaret();
    if (caret instanceof DefaultCaret)
    {
      ( (DefaultCaret) caret).setUpdatePolicy(
          DefaultCaret.NEVER_UPDATE);
    } // end if DefaultCaret

    // remove registered UndoableEditListeners if any
    Document doc = textPane.getDocument();
    if (doc instanceof AbstractDocument)
    {
      UndoableEditListener[] undoListeners = 
          ( (AbstractDocument) doc).getUndoableEditListeners();
      if (undoListeners.length > 0)
      {
        for (UndoableEditListener undoListener : undoListeners)
        {
          doc.removeUndoableEditListener(undoListener);
        } // end for undoListener
      } // end if undoListeners
    } // end if AbstractDocument
  } // end method tryToFixMemory

  private class ButtonHandler implements ActionListener
  {
    private final int iter;

    ButtonHandler(int iter)
    {
      this.iter = iter;
    } // end constructor

    @Override
    public void actionPerformed(ActionEvent e)
    {
      for (int i = 0; i < iter; i++)
      {
        append(String.format("%10d
", i) );
      } // end for i
    } // end method actionPerformed

  } // end class ButtonHandler

} // end class TestMain

JVM 来自用于 Linux x64 的官方 Oracle Java SE 开发工具包 8u45.所有测试均在 -Xmx100m 限制下完成.

JVM was from official Oracle Java SE Development Kit 8u45 for Linux x64. All tests were done with -Xmx100m limit.

按预期工作:

GUI 在中间点冻结:

GUI freezes at an intermediate point:

内存泄漏:

在某些时候没有剩余内存,我收到以下错误:

At some point there is no memory left and I got the following error:

Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: GC overhead limit exceeded
  at java.util.Formatter.parse(Formatter.java:2560)
  at java.util.Formatter.format(Formatter.java:2501)
  at java.util.Formatter.format(Formatter.java:2455)
  at java.lang.String.format(String.java:2928)
  at memleak.TestMain$ButtonHandler.actionPerformed(TestMain.java:117)
  at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2022)
  at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2346)
  at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:402)
  at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
  at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:252)
  at java.awt.Component.processMouseEvent(Component.java:6525)
  at javax.swing.JComponent.processMouseEvent(JComponent.java:3324)
  at java.awt.Component.processEvent(Component.java:6290)
  at java.awt.Container.processEvent(Container.java:2234)
  at java.awt.Component.dispatchEventImpl(Component.java:4881)
  at java.awt.Container.dispatchEventImpl(Container.java:2292)
  at java.awt.Component.dispatchEvent(Component.java:4703)
  at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4898)
  at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4533)
  at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4462)
  at java.awt.Container.dispatchEventImpl(Container.java:2278)
  at java.awt.Window.dispatchEventImpl(Window.java:2750)
  at java.awt.Component.dispatchEvent(Component.java:4703)
  at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758)
  at java.awt.EventQueue.access$500(EventQueue.java:97)
  at java.awt.EventQueue$3.run(EventQueue.java:709)
  at java.awt.EventQueue$3.run(EventQueue.java:703)
  at java.security.AccessController.doPrivileged(Native Method)
  at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75)
  at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:86)
  at java.awt.EventQueue$4.run(EventQueue.java:731)
  at java.awt.EventQueue$4.run(EventQueue.java:729)
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: GC overhead limit exceeded
  at javax.swing.text.GlyphPainter1.modelToView(GlyphPainter1.java:147)
  at javax.swing.text.GlyphView.modelToView(GlyphView.java:653)
  at javax.swing.text.CompositeView.modelToView(CompositeView.java:265)
  at javax.swing.text.BoxView.modelToView(BoxView.java:484)
  at javax.swing.text.ParagraphView$Row.modelToView(ParagraphView.java:900)
  at javax.swing.text.CompositeView.modelToView(CompositeView.java:265)
  at javax.swing.text.BoxView.modelToView(BoxView.java:484)
  at javax.swing.text.CompositeView.modelToView(CompositeView.java:265)
  at javax.swing.text.BoxView.modelToView(BoxView.java:484)
  at javax.swing.plaf.basic.BasicTextUI$RootView.modelToView(BasicTextUI.java:1509)
  at javax.swing.plaf.basic.BasicTextUI.modelToView(BasicTextUI.java:1047)
  at javax.swing.text.DefaultCaret.repaintNewCaret(DefaultCaret.java:1308)
  at javax.swing.text.DefaultCaret$1.run(DefaultCaret.java:1287)
  at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
  at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756)
  at java.awt.EventQueue.access$500(EventQueue.java:97)
  at java.awt.EventQueue$3.run(EventQueue.java:709)
  at java.awt.EventQueue$3.run(EventQueue.java:703)
  at java.security.AccessController.doPrivileged(Native Method)
  at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75)
  at java.awt.EventQueue.dispatchEvent(EventQueue.java:726)
  at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
  at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
  at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
  at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
  at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
  at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
Exception in thread "RMI TCP Connection(idle)" java.lang.OutOfMemoryError: GC overhead limit exceeded
  at java.lang.Thread.getName(Thread.java:1135)
  at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:677)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
  at java.lang.Thread.run(Thread.java:745)

详细的内存统计数据显示 java.awt.event.InvocationEventsun.awt.EventQueueItemjavax.swing.text.DefaultCaret 的计数非常高$1(固定版本中没有):

Detailed memory statistics show very high counts for java.awt.event.InvocationEvent, sun.awt.EventQueueItem, and javax.swing.text.DefaultCaret$1 (absent in the fixed version):

设置 FIX = true 并没有改善这种情况.

Setting FIX = true did not improve the situation.

现在显示插入符号位置没有更新:

Now shows that the caret position is not updated:

有效并且没有内存泄漏的迹象:

Works and has no indication of memory leak:

推荐答案

造成这种情况的原因是您在事件调度线程中运行 for 循环(参见 事件调度线程).这是所有用户界面交互发生的线程.

The reason for this is that you are running the for loop in the event dispatch thread (see Event Dispatch Thread). This is the thread in which all user interface interactions happens.

如果您正在运行一个很长的任务,那么您应该在不同的线程中运行它,以便用户界面保持响应.如果您需要对用户界面进行更改,例如更改 JTextPane 中的文本并将插入符号位置设置为与事件调度线程不同的线程,则需要调用 EventQueue.invokeLater()EventQueue.invokeAndWait()(参见 EventQueue).

If you are running a long task, then you should run it in a different thread, so that the user interface keeps responsive. If you need to make a change on the user interface, like changing text in your JTextPane and setting the caret position out of a different thread than the event dispatch thread, you need to invoke either EventQueue.invokeLater() or EventQueue.invokeAndWait() (see EventQueue).

我认为设置插入符号位置的触发事件在您的案例中排队,并且只能在循环完成时进行处理(因为两者都在事件调度线程中处理).所以你应该尝试这样的事情:

I think the triggered events from setting the caret position are queued in your case and can only be processed, when the loop is finished (cause both are processed in event dispatch thread). So you should try something like this:

@Override
public void actionPerformed(ActionEvent e)
{
  new Thread(new Runnable() {
    @Override
    public void run() {
      for (int i = 0; i < iter; i++)
      {
        final String display = String.format("%10d
", i);
        try {
          EventQueue.invokeAndWait(new Runnable() {
            @Override
            public void run() {
              append(display);
            }
          });
        } catch (Exception e) {
          e.printStackTrace();
        }
      } // end for i
    }
  }).start();
} 

如果只在 x 次迭代后调用 EventQueue.invokeAndWait 并缓存需要显示的先前结果,可能会更好.

Might be even better if you call EventQueue.invokeAndWait only after x iterations and cache the previous results, which needs to be displayed.

这篇关于如何防止 JTextPane.setCaretPosition(int) 中的内存泄漏?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆