I have the following issue with an app of mine, a basic IRC tool, which adds messages to a "JTextPane" with using "HTMLEditorKit" as an output GUI. I noticed, that randomly but over time, my app was using more and more memory, easily blowing up in crowded channels to already 300MB after just about 20 minutes of usage. I think the problem is somehow related to "JTextPane", because I can reproduce the issue with this code:
package javaapplication26;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultCaret;
import javax.swing.text.Element;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
public class NewJFrame extends javax.swing.JFrame {
private long globalCount = 0;
/**
* Creates new form NewJFrame
*/
public NewJFrame() {
initComponents();
this.setSize(500, 200);
this.setLocationRelativeTo(null);
this.jTextPane1.setEditorKit(new HTMLEditorKit());
this.jTextPane1.setContentType("text/html");
this.jTextPane1.setText("<html><body><div id=\"GLOBALDIV\"></div></body></html>");
this.jScrollPane1.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
this.jScrollPane1.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
DefaultCaret caret = (DefaultCaret) this.jTextPane1.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
this.jScrollPane1.setAutoscrolls(false);
this.jTextPane1.setAutoscrolls(false);
Thread fillThread = new Thread() {
@Override
public void run() {
while (!interrupted()) {
try {
removeFromPane(jTextPane1);
insertHTMLToPane(jTextPane1, "<div>"+globalCount+"</div>");
Thread.sleep(1);
} catch (InterruptedException ex) {
Logger.getLogger(NewJFrame.class.getName()).log(Level.SEVERE, null, ex);
break;
}
}
}
};
fillThread.start();
}
private void removeFromPane(JTextPane pane) {
HTMLDocument doc = (HTMLDocument) pane.getDocument();
Element element = doc.getElement("ID" + (this.globalCount - 10));
if (element != null) {
doc.removeElement(element);
}
}
private void insertHTMLToPane(JTextPane pane, String htmlCode) {
this.globalCount++;
HTMLDocument doc = (HTMLDocument) pane.getDocument();
Element element = doc.getElement("GLOBALDIV");
if (element != null) {
try {
doc.insertBeforeEnd(element, "<div id=\"ID"+this.globalCount+"\">" + htmlCode + "</div>");
} catch (BadLocationException | IOException ex) {
Logger.getLogger(NewJFrame.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
jPanel1 = new javax.swing.JPanel();
jScrollPane1 = new javax.swing.JScrollPane();
jTextPane1 = new javax.swing.JTextPane();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
jPanel1.setLayout(new java.awt.BorderLayout());
jScrollPane1.setViewportView(jTextPane1);
jPanel1.add(jScrollPane1, java.awt.BorderLayout.CENTER);
getContentPane().add(jPanel1, java.awt.BorderLayout.CENTER);
pack();
}// </editor-fold>
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
/* Set the Nimbus look and feel */
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
* For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new NewJFrame().setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.JPanel jPanel1;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTextPane jTextPane1;
// End of variables declaration
}
The weird thing is, it doesnt happen with 100% chance when letting it run in Netbeans. Sometimes it stays around 70MB and never grows, but then running another time, it randomly explodes, and already grows to about 200-250MB after a minute or two.
I dont really know whats the data in memory growing more and more. It seems removing a line via "doc.removeElement(element)" doesnt always flags the object behind it to be cleared with next GC routine.
Letting it run in Netbeans with the profiler, I get something like this:
It seems there is some kind of "undo mechanism" keeping reference to all inserted lines? I am no expert in using the profiler though because I am not getting some logic out of it, where things like char[] and some other growing into the thousands, even if nothing happens in the program.
This though seems to hint, that whatever reason for, the JTextPane creates for each insert a new StyleSheet and keeps it forever in a HashTable:
I would welcome any help to find out why this is happening, or how to fix the issue. I am using latest 64bit JDK of course under Windows 10. Thank you very much