私のチャット クライアントには、テキストが挿入される JTextPane があり、1 秒あたり最大数行まで可能です。通常は、長時間 (たとえば 1 時間) であっても正常に動作しますが、非常に遅くなり、多くの 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)
また、JConsole でプログラムを監視したところ、より多くのリソースを使用し始めたときに、チャットログに認識できない文字がいくつかあることに気付きました (たとえば、顔文字、ある種のインド文字、ある種のタイ文字として使用されていました)。絵文字の一部)。同じ文字を自分で JTextPane に挿入しようとしましたが、それ自体に異常に時間がかかり、その後のテキスト挿入が大幅に遅くなるという効果もありました。
問題を再現できる SSCCE を作成しました。
- 明らかに何かを壊す文字を挿入した後..
- ..改行が挿入されない場合、数百行後にはかなり遅くなります。
- ..数百行が既に存在する場合、挿入ごとに StyledDocument に追加されたスタイルを変更すると、非常に遅くなります。
- ..それ以外の場合は、わずかに遅くなります (CPU 使用率が数パーセント増加します) が、メモリの使用量が徐々に増えていきます。
改行を追加しないと、挿入されたすべてのテキストが 1 つのエンティティとして扱われると思いますが、StyledDocument に追加された Style を変更すると、ドキュメント全体が何らかの形で更新される可能性があります。すでに挿入されたテキスト。
これが SSCCE (jdk1.7.0_21 でテスト済み) で、単純なコマンド入力を使用しています。 StyledDocument と別の「改行」に追加されたスタイルを変更すると、改行を追加するかどうかが切り替わります。その他の入力は、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("");
}
}
}
問題は今、そこで何が起こっているかです。既知のバグですか?私は何か間違ったことをしていますか?キャラクターを1つ追加するだけでその効果が得られるのは奇妙に思えます。レンダリングのコストが多少高くなったとしても、それほど問題にはならないはずです。
Java のバグである場合、回避策として何ができますか? 影響を受けたキャラクターを何らかの方法でフィルタリングすることはできますか?しかし、私はそれらがどれであるかさえ知りません。私が何か間違ったことをしている場合、それは何ですか? テキストを挿入する前に、何らかの方法でテキストを準備する必要があるのでしょうか。エンコーディングを変更しますか?多分それは私が変更する必要がある非常に基本的で単純なものですか?助けてください。:)
更新: 次の図は、5000 行のテキスト (約 20 分かかります) を挿入しているときに何が起こるかを示しています。終了後に JConsole でガベージ コレクションを要求したところ、左のコレクションは約 10 MB まで減少しましたが、右のコレクションは約 45 MB までしか減少しませんでした。唯一の違いが挿入された文字 1 つだけであることを考慮すると、これは大幅に大きくなっています。その後のドロップは、JConsole の切断だけです。また、右側の方が CPU 使用率が約 0.5% 高くなっていることもわかります。このテストを数回繰り返しましたが、結果は常に同じでした。これには、問題をさらに目立たせる改行/スタイルがありません。