Java 如何防止JTextPane.setCaretPosition(int)中的内存泄漏?
我正在使用基于Swing的GUI开发Java应用程序。应用程序使用Java 如何防止JTextPane.setCaretPosition(int)中的内存泄漏?,java,swing,memory-leaks,jtextpane,caret,Java,Swing,Memory Leaks,Jtextpane,Caret,我正在使用基于Swing的GUI开发Java应用程序。应用程序使用JTextPane输出日志消息,如下所示:1)截断现有文本,使总文本大小保持在限制范围内;2) 添加新文本;3) 滚动到末尾(实际逻辑略有不同,但此处不相关) 我使用Eclipse和JVM监视器来确定合理的文本大小限制,并发现了严重的内存泄漏。我试图从基础文档中删除UndoableEditListeners并禁用自动插入符号位置更新(通过使用DefaultCaret.NEVER\u UPDATE和JTextPane.setCare
JTextPane
输出日志消息,如下所示:1)截断现有文本,使总文本大小保持在限制范围内;2) 添加新文本;3) 滚动到末尾(实际逻辑略有不同,但此处不相关)
我使用Eclipse和JVM监视器来确定合理的文本大小限制,并发现了严重的内存泄漏。我试图从基础文档中删除UndoableEditListener
s并禁用自动插入符号位置更新(通过使用DefaultCaret.NEVER\u UPDATE
和JTextPane.setCaretPosition(int)
显式更改位置),但没有成功。最后,我决定完全禁用更改插入符号的位置,这修复了泄漏
我有两个问题:
FIX
和FIXXX
标志对应于我修复内存泄漏的尝试
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\n", i) );
} // end for i
} // end method actionPerformed
} // end class ButtonHandler
} // end class TestMain
详细的内存统计信息显示java.awt.event.InvocationEvent
、sun.awt.EventQueueItem
、和javax.swing.text.DefaultCaret$1
的计数非常高(在固定版本中不存在):
设置FIX=true
并没有改善这种情况
两个标志均为真
:
小测验
现在显示插入符号位置未更新:
压力测试
工作正常且无内存泄漏迹象:
原因是您正在事件分派线程中运行for循环(请参阅)。这是发生所有用户界面交互的线程 如果您正在运行一个长任务,那么您应该在另一个线程中运行它,以便用户界面保持响应。如果需要在用户界面上进行更改,例如更改JTextPane中的文本并将插入符号位置设置为事件分派线程以外的其他线程,则需要调用
EventQueue.invokeLater()
或EventQueue.invokeAndWait()
(请参阅)
我认为设置插入符号位置触发的事件在您的案例中是排队的,并且只能在循环完成时处理(因为这两个事件都是在事件分派线程中处理的)。所以你应该试试这样:
@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\n", i);
try {
EventQueue.invokeAndWait(new Runnable() {
@Override
public void run() {
append(display);
}
});
} catch (Exception e) {
e.printStackTrace();
}
} // end for i
}
}).start();
}
@覆盖
已执行的公共无效操作(操作事件e)
{
新线程(newrunnable()){
@凌驾
公开募捐{
对于(int i=0;i
如果您只在x次迭代后调用
EventQueue.invokeAndWait
并缓存以前需要显示的结果,可能会更好。另请参见。@Andrey您是否找到了导致它的原因?我的一个使用HTMLEditorKit的应用程序也存在同样的问题,我无法找到修复内存泄漏的方法。下面是一个我从未得到答案的例子:@M.H.是的,请检查下面的答案。简言之,在事件处理程序中追加字符串,然后更新插入符号位置将触发另一个事件,该事件将一直排队,直到处理完所有字符串。如果这个队列很小,那么就没有问题,否则JVM就会被卡住,因为没有内存来处理事件。附加1个字符的10000个字符串和一个10000个字符的字符串之间有很大的区别。@andrey我真的不知道你的意思。实际上,我看过你的代码,但我不知道如何修复它。你看过我的例子吗?在那里,我实际上是一个线程,所以怎么可能有一个队列?排什么队?我还将文本窗格设置为noteditable,并且从不更改插入符号。我也不会通过htmleditorkit附加我使用insertBeforeEnd的字符串。@M.H.您提供的链接给了我404,因此我无法对此发表评论。在我的例子中,解决方案是构建一个长字符串
,然后立即附加它,而不是在循环中,因此我避免多次调用setCaretPosition()
。我不确定这是否可行。你试过这个密码吗?应该注意的是,我使用带有String.format()
的循环只是为了模拟真实数据,对这一部分的优化毫无价值。无论如何,问题在于textPane.setCaretPosition(doc.getLength())代码>,不带字符串。我本来打算接受您的答案,但您的代码无法编译。我没有足够的声誉编辑它,只是建议编辑。然而,我的编辑是错误的。你能自己更新代码吗?@sfrutig我不这么认为。我的一个使用HTMLEditorKit的应用程序也存在同样的问题,我无法找到修复内存泄漏的方法。下面是一个我从未得到答案的例子:
@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\n", i);
try {
EventQueue.invokeAndWait(new Runnable() {
@Override
public void run() {
append(display);
}
});
} catch (Exception e) {
e.printStackTrace();
}
} // end for i
}
}).start();
}