My chat client has a JTextPane in which text is inserted, which can be up to several lines per second. It usually works fine even for a longer period of time (for example, per hour), but sometimes it just becomes incredibly slow, using a lot of CPU and memory, sometimes up to 1 GB and almost completely freezing.
I added the option β-Xrunhprof: heap = sitesβ to find out what memory is using, and from what I could collect, it has something to do with text rendering, although I really don't know about it, so it's more educated a guess. This is part of the result made at a time when memory usage was unusually high. I included an appropriate trace in each entry. Other heap heaps looked a bit different, but always pointed to the same or similar classes (something with a character in the name). You do not know how to correctly interpret this, and if it is really useful for solving this problem.
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)
I also tracked the program using JConsole and noticed that only when it started to use more resources in the chat that I did not recognize were there some characters (for example, a smiley face, some Indian character and some Thai character, were used as part of the emoticon). I tried to insert the same characters into the JTextPane myself, which took an unusually long character, and also led to the fact that subsequent text inserts were much slower.
I created SSCCE with which I could reproduce the problem:
- After entering a character that seems to have broken something.
- .. it becomes much slower after several hundred lines if no more lines are inserted.
- .. it becomes much slower when changing the style that was added to the StyledDocument with each insert, if there are already several hundred lines.
- .. otherwise, it becomes a little slower (several percent more CPU usage), but gradually uses more and more memory.
I suppose that not adding linebreak treats all inserted text as one object, but changing the style added to StyledDocument can somehow update the whole document, although I did not know about it, since it does not actually change the style of already inserted text.
Now here is SSCCE (tested with jdk1.7.0_21), with a simple command input: "test" adds several identical lines, "insert1" or "insert2" adds a character that slows everything down, "style" "changes between changing the style that was added to StyledDocument, and the other, "linebreak" toggles between adding lines and not. Another input is just added to 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(""); } } }
The question is what happens there. Is this a known bug? Am I doing something wrong? It seems strange that adding one character will have such an effect. Even if it would be a little more expensive, it should not cause big problems.
If this is a Java error, what can I do as a workaround? Maybe filter the affected characters? But I donβt even know what it is. If I do something wrong, what is it? Maybe I need to somehow prepare the text before embedding it? Change its encoding? Maybe this is something very simple and simple I need to change? Please help. :)
Update: The following figure shows what happens when you enter 5,000 lines of text (which takes about 20 minutes), on the left, without doing anything special, on the right after inserting one of the unpleasant characters. I asked for garbage collection in JConsole after its completion, and the left one is up to 10 MB, and the right one is only about 45 MB, which is much more, given that the only difference is the one character inserted. After that, JConsole simply shuts down. You can also see that CPU usage is about 0.5 percentage points higher on the right. I repeated this test several times, the result was always the same. This is without linebreak / Style elements, which makes the problem even more noticeable.
