use*_*667 7 java unicode swing memory-leaks jtextpane
我的聊天客户端有一个JTextPane,其中插入了文本,最多可以达到每秒几行.它通常工作正常,即使是较长的时间(例如一小时),但有时它会变得非常慢,使用大量的CPU和内存,有时高达1GB并且几乎完全冻结.
我添加了"-Xrunhprof:heap = sites"参数来查找使用内存的内容以及我可以收集的内容,它与文本呈现有关,虽然我真的不知道这些内容,所以它更多一个有根据的猜测.这是结果的一部分,在内存使用异常高时采用.我在每个条目下都包含了适当的跟踪.其他堆转储看起来略有不同,但它总是指向相同或类似的类(名称中包含Glyph的东西).不确定如何正确解释这一点,以及它是否真的有助于解决这个问题.
percent live alloc'ed stack class
rank self accum bytes objs bytes objs trace name
1 16.33% 16.33% 11209120 350285 99416352 3106761 319103 java.awt.geom.Rectangle2D$Float
TRACE 319103:
java.awt.geom.RectangularShape.<init>(RectangularShape.java:56)
java.awt.geom.Rectangle2D.<init>(Rectangle2D.java:511)
java.awt.geom.Rectangle2D$Float.<init>(Rectangle2D.java:111)
sun.font.StandardGlyphVector$GlyphStrike.getGlyphOutlineBounds(StandardGlyphVector.java:1790)
2 14.28% 30.61% 9799744 3958 52026864 49485 319095 float[]
TRACE 319095:
sun.font.StandardGlyphVector.getGlyphInfo(StandardGlyphVector.java:851)
sun.font.ExtendedTextSourceLabel.createCharinfo(ExtendedTextSourceLabel.java:583)
sun.font.ExtendedTextSourceLabel.getCharinfo(ExtendedTextSourceLabel.java:509)
sun.font.ExtendedTextSourceLabel.getLineBreakIndex(ExtendedTextSourceLabel.java:455)
3 8.17% 38.77% 5604560 350285 49708176 3106761 319110 sun.font.DelegatingShape
TRACE 319110:
sun.font.DelegatingShape.<init>(DelegatingShape.java:43)
sun.font.StandardGlyphVector.getGlyphVisualBounds(StandardGlyphVector.java:586)
sun.font.StandardGlyphVector.getGlyphInfo(StandardGlyphVector.java:864)
sun.font.ExtendedTextSourceLabel.createCharinfo(ExtendedTextSourceLabel.java:583)
4 7.96% 46.74% 5466576 9933 40683104 164341 319090 float[]
TRACE 319090:
sun.font.GlyphLayout$GVData.createGlyphVector(GlyphLayout.java:596)
sun.font.GlyphLayout.layout(GlyphLayout.java:476)
sun.font.ExtendedTextSourceLabel.createGV(ExtendedTextSourceLabel.java:325)
sun.font.ExtendedTextSourceLabel.getGV(ExtendedTextSourceLabel.java:311)
5 4.07% 50.81% 2795304 9933 21434888 164341 319089 int[]
TRACE 319089:
sun.font.GlyphLayout$GVData.createGlyphVector(GlyphLayout.java:591)
sun.font.GlyphLayout.layout(GlyphLayout.java:476)
sun.font.ExtendedTextSourceLabel.createGV(ExtendedTextSourceLabel.java:325)
sun.font.ExtendedTextSourceLabel.getGV(ExtendedTextSourceLabel.java:311)
6 3.71% 54.52% 2544072 106003 183421728 7642572 319087 java.awt.geom.Point2D$Float
TRACE 319087:
java.awt.geom.Point2D.<init>(Point2D.java:237)
java.awt.geom.Point2D$Float.<init>(Point2D.java:69)
sun.font.FileFontStrike.getGlyphMetrics(FileFontStrike.java:791)
sun.font.FileFontStrike.getGlyphMetrics(FileFontStrike.java:787)
7 3.70% 58.22% 2539560 105815 182834016 7618084 319088 java.awt.geom.Point2D$Float
TRACE 319088:
java.awt.geom.Point2D.<init>(Point2D.java:237)
java.awt.geom.Point2D$Float.<init>(Point2D.java:69)
sun.font.FileFontStrike.getGlyphMetrics(FileFontStrike.java:809)
sun.font.FileFontStrike.getGlyphMetrics(FileFontStrike.java:787)
8 2.20% 60.42% 1512888 6109 14728808 123309 319100 java.awt.Shape[]
TRACE 319100:
sun.font.StandardGlyphVector.getGlyphVisualBounds(StandardGlyphVector.java:580)
sun.font.StandardGlyphVector.getGlyphInfo(StandardGlyphVector.java:864)
sun.font.ExtendedTextSourceLabel.createCharinfo(ExtendedTextSourceLabel.java:583)
sun.font.ExtendedTextSourceLabel.getCharinfo(ExtendedTextSourceLabel.java:509)
9 2.20% 62.62% 1507120 2151 49362432 73824 319503 float[]
TRACE 319503:
sun.font.StandardGlyphVector.getGlyphInfo(StandardGlyphVector.java:851)
sun.font.ExtendedTextSourceLabel.createCharinfo(ExtendedTextSourceLabel.java:583)
sun.font.ExtendedTextSourceLabel.getCharinfo(ExtendedTextSourceLabel.java:509)
sun.font.ExtendedTextSourceLabel.getCharX(ExtendedTextSourceLabel.java:353)
10 2.09% 64.71% 1437120 44910 99416352 3106761 319111 java.awt.geom.Rectangle2D$Float
TRACE 319111:
java.awt.geom.RectangularShape.<init>(RectangularShape.java:56)
java.awt.geom.Rectangle2D.<init>(Rectangle2D.java:511)
java.awt.geom.Rectangle2D$Float.<init>(Rectangle2D.java:128)
java.awt.geom.Rectangle2D$Float.getBounds2D(Rectangle2D.java:251)
11 1.84% 66.55% 1262456 6 1707160 18 307780 char[]
TRACE 307780:
javax.swing.text.GapContent.allocateArray(GapContent.java:94)
javax.swing.text.GapVector.resize(GapVector.java:214)
javax.swing.text.GapVector.shiftEnd(GapVector.java:229)
javax.swing.text.GapContent.shiftEnd(GapContent.java:345)
12 1.16% 67.71% 794640 9933 13147280 164341 319092 sun.font.StandardGlyphVector
TRACE 319092:
java.awt.font.GlyphVector.<init>(GlyphVector.java:109)
sun.font.StandardGlyphVector.<init>(StandardGlyphVector.java:185)
sun.font.GlyphLayout$GVData.createGlyphVector(GlyphLayout.java:607)
sun.font.GlyphLayout.layout(GlyphLayout.java:476)
Run Code Online (Sandbox Code Playgroud)
我还用JConsole监视程序并注意到,当它开始使用更多的资源时,聊天记录中有一些我无法识别的字符(例如表情符号,某种印度字符和某种泰语字符用作表情的一部分).我自己尝试将相同的字符插入到JTextPane中,这本身花了很长时间,并且还使后续文本插入速度慢得多.
我创建了一个SSCCE,我可以用它来重现这个问题:
我想不添加换行符将所有插入的文本视为一个实体,而更改已添加到StyledDocument的样式可能会以某种方式更新整个文档,虽然我不知道这一点,因为它实际上并没有改变样式已插入文字.
现在这里是SSCCE(用jdk1.7.0_21测试),带有一个简单的命令输入:"test"添加了许多相同的行,"insert1"或"insert2"添加了一个减慢一切的字符,"样式"之间的变化更改已添加到StyledDocument的样式和另一个样式,"linebreak"在添加linesbreaks之间切换,而不是.其他输入只是直接添加到JTextPane.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import javax.swing.text.*;
public class JTextPaneTest extends JFrame implements Runnable, ActionListener {
JTextPane textPane;
JTextField input;
Style styleA;
SimpleAttributeSet styleB;
StyledDocument doc;
boolean setStyleA = false;
boolean linebreak = true;
public JTextPaneTest() {
SwingUtilities.invokeLater(this);
}
@Override
public void run() {
// Text Pane
textPane = new JTextPane();
doc = textPane.getStyledDocument();
JScrollPane scrollPane = new JScrollPane(textPane);
// Styles
styleA = doc.addStyle("styleA", null);
styleB = new SimpleAttributeSet();
// Input
input = new JTextField();
input.addActionListener(this);
// Add everything to the window
this.getContentPane().add(scrollPane, BorderLayout.CENTER);
getContentPane().add(input, BorderLayout.SOUTH);
// Prepare and show window
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
this.setSize(400, 300);
setVisible(true);
}
public static void main(String[] args) {
new JTextPaneTest();
}
void insert(final String text) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
if (setStyleA) {
// Changing styleA, which is added to the StyledDocument
// seems to make the problem worse
StyleConstants.setForeground(styleA, Color.blue);
}
else {
StyleConstants.setForeground(styleB, Color.blue);
}
// Not adding a linebreak seems to make the problem worse
String addLinebreak = "";
if (linebreak) {
addLinebreak = "\n";
}
doc.insertString(doc.getLength(), text+addLinebreak, null);
} catch (BadLocationException ex) {
Logger.getLogger(JTextPaneTest.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
@Override
public void actionPerformed(ActionEvent e) {
String text = input.getText();
if (text.equals("test")) {
new Thread(new Runnable() {
@Override
public void run() {
// Insert some text to kind of simulate chat messages coming in
for (int i = 0; i < 500; i++) {
try {
Thread.sleep(250);
} catch (InterruptedException ex) {
Logger.getLogger(JTextPaneTest.class.getName()).log(Level.SEVERE, null, ex);
}
insert(i + " Test text to sort of simulate a chat message");
}
}
}).start();
}
// Insert text that seems to break something
// Example 1:
else if (text.equals("insert1")) {
insert("\uD83D\uDE3A");
}
// Example 2:
else if (text.equals("insert2")) {
insert("\u0E07");
}
// Toggle changing styleA or styleB
else if (text.equals("style")) {
if (this.setStyleA) {
setStyleA = false;
insert("Style: B");
}
else {
setStyleA = true;
insert("Style: A");
}
}
// Toggle printing a linebreak after each insert
else if (text.equals("linebreak")) {
if (this.linebreak) {
linebreak = false;
insert("Linebreak: OFF");
}
else {
linebreak = true;
insert("Linebreak: ON");
}
}
// Output entered text
else {
insert(input.getText());
input.setText("");
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在的问题是,那里发生了什么.这是一个已知的bug吗?难道我做错了什么?添加单个字符会产生这种效果似乎很奇怪.即使渲染成本稍高,也不应该造成太多麻烦.
如果是Java错误,我可以做什么作为解决方法?也许以某种方式过滤受影响的角色?但我甚至都不知道那是哪一个.如果我做错了什么,那是什么?也许我必须在插入之前以某种方式准备文本?改变它的编码?也许这是我需要改变的非常基本和简单的东西?请帮忙.:)
更新:下图显示插入5000行文本时(大约需要20分钟),左侧没有做任何特殊操作,在插入一个麻烦的字符后右侧.我在JConsole完成后请求了垃圾收集,左边的垃圾收集大约下降到10 MB,而右边的垃圾收集只下降到大约45 MB,这要多得多,考虑到唯一的区别是一个插入的字符.之后的下降只是JConsole断开连接.您还可以看到CPU使用率在右侧高出约0.5个百分点.我重复了几次这个测试,结果总是一样的.没有linebreak/Style的东西使问题更加明显.
这就是我所做的:
我看到你在问题中提到的内容,但想补充一下:
特殊字符确实使用与正常示例文本不同的渲染路径。例如,比较快照 (3) 和 (5) 之间的差异,仅显示包中的一个类sun.font.*
。快照 (5) 和 (8) 之间的差异显示现在使用了额外的约 40 个类。其中包括您提到的类:sun.font.StandardGlyphVector
、sun.font.ExtendedTextSourceLabel
、sun.font.StandardTextSource
和sun.font.DelegatingShape
。
在我的分析运行中,上述类中的大多数都有大约 850 个活动对象。但sun.font.DelegatingShape
它是一个异常值,约有 20,000 多个活动对象。
我使用 JVisualVM 来探索最终的堆转储并重点关注 DelegatingShape 类。这些对象持有对不同java.awt.geom.Rectangle2D$Float
对象的引用。这两者都由Shape[]
内部数组保持活动状态StandardGlyphVector
并与 共享ExtendedTextSourceLabel
。每个数组包含约 49 个非空元素。
查看源代码,这些数组由软引用保存,作为单个字形的视觉边界框的一种缓存(请参阅:StandardGlyphVector.getGlyphVisualBounds()
)。好消息是只能通过软引用访问的对象可以被垃圾收集,并且它们本身不会直接构成内存泄漏。虚拟机将尽可能长时间地将它们保留在内存中(增加堆)。如果这些物品被其他方式牢牢持有,那么它们将永远不会被收集;目前我没有注意到任何明显的强引用。
但为什么有这么多 ExtendedTextSourceLabels?长话短说,您的JTextPane
实现是在其之上实现的javax.swing.text.BoxView
,在通过文档插入约 1002 行后,包含约 4004ParagraphView
个子对象。每个视图都包含自己的实例TextLayoutStrategy
,并且在遍历大量其他对象之后,保存这些ExtendedTextSourceLabel
实例。
因此,支持 Unicode 的某些子集可能会更加昂贵,无论是渲染时间还是内存消耗。我没有发现任何内存“泄漏”的迹象,除了您的示例将“聊天对话”的整个历史记录保留在 JTextPane 样式文档中的情况之外。你能做什么?
仅在 JTextPane 中显示聊天历史记录的有限部分,例如仅显示最近的N个条目。
将聊天历史记录保存在 Swing 渲染图之外的其他一些数据结构中。您需要自己管理滚动到 JTextPane 中文本输入/输出的“页入”和“页出”部分,因此它只需要呈现整个历史记录的一小部分。
编辑:分析运行#2
"AWT-EventQueue-0" prio=10 tid=0x00007ff38028c000 nid=0x5f74 runnable [0x00007ff3745db000]
java.lang.Thread.State: RUNNABLE
at javax.swing.text.AbstractDocument$BranchElement.getElementIndex(AbstractDocument.java:2389)
at javax.swing.text.CompositeView.getViewIndexAtPosition(CompositeView.java:579)
at javax.swing.text.FlowView$LogicalView.getViewIndexAtPosition(FlowView.java:692)
at javax.swing.text.CompositeView.getViewIndex(CompositeView.java:497)
at javax.swing.text.TextLayoutStrategy$AttributedSegment.getAttribute(TextLayoutStrategy.java:520)
at sun.text.bidi.BidiBase.setPara(BidiBase.java:2711)
at java.text.Bidi.<init>(Bidi.java:134)
at java.awt.font.TextMeasurer.initAll(TextMeasurer.java:208)
at java.awt.font.TextMeasurer.<init>(TextMeasurer.java:167)
at java.awt.font.LineBreakMeasurer.<init>(LineBreakMeasurer.java:310)
Run Code Online (Sandbox Code Playgroud)
当“换行符关闭”时,性能会陷入停滞。我进行了多个线程转储,共同点是 LineBreakMeasurer;我选择上面的跟踪是因为它显示它必须处理“bidi”(双向)字符。
只要我不触及样式或换行选项,这对我来说似乎不是问题。
归档时间: |
|
查看次数: |
960 次 |
最近记录: |