JTextPane/HTMLEditorKit内存泄漏 [英] JTextPane / HTMLEditorKit memory leak

查看:72
本文介绍了JTextPane/HTMLEditorKit内存泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的一个应用程序(基本的IRC工具)遇到以下问题,该应用程序使用"HTMLEditorKit"作为输出GUI将消息添加到"JTextPane".我注意到,随机但随着时间的流逝,我的应用程序正在使用越来越多的内存,仅在使用了大约20分钟后,它就在拥挤的频道中轻易地膨胀到了300MB.我认为问题与"JTextPane"有关,因为我可以使用以下代码重现该问题:

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                   
}

奇怪的是,当它在Netbeans中运行时,它不会100%地发生.有时它会保持在70MB左右,再也不会增长,但是随后又运行一次,它会随机爆炸,在一两分钟后已经增长到200-250MB.

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.

我真的不知道越来越多的内存中的数据是什么.似乎通过"doc.removeElement(element)"删除一行并不总是将后面的对象标记为要通过下一个GC例程清除.

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.

让它使用探查器在Netbeans中运行,我得到的是这样的:

Letting it run in Netbeans with the profiler, I get something like this:

似乎有某种撤消机制"保持对所有插入行的引用?我不是使用探查器的专家,因为我没有从探查器中获得一些逻辑,即使程序中什么也没发生,像char []之类的东西会发展到成千上万种.

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.

这似乎暗示着,无论出于何种原因,JTextPane都会为每个插入的内容创建一个新的StyleSheet并将其永久保存在HashTable中:

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:

我将竭诚为您提供帮助,以找出发生这种情况的原因或解决此问题的方法.我当然在Windows 10下使用最新的64位JDK.非常感谢

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

推荐答案

您的fillThread确实很吓人,而且毫无意义,出于事件考虑,请不要那样做.

Your fillThread is really scary and non-sense, for event sake, please do not go that way.

该线程是lets eat resources的一种,除了java本身很重之外,它的确在系统中造成很大混乱.

This thread is kind of lets eat resources, and beside java itself is heavy enough, it could make a great mess in system indeed.

您可以根据事件来调用您的业务(删除最后一条消息并添加新消息),在这里您可以依靠单击按钮或在文本框中输入键.

You may invoke you business(remove last message and add new one) based on an event, here you may rely on a button click or enter key over the textbox.

即使您正在使用无状态的内容(例如http),也可以将睡眠时间增加到更有意义的时间,可能是1秒,甚至更长.

Even if you are working with a stateless stuff(like http), you may increase the sleep time to something more senseful, maybe 1 second, or even more.

一个可能的问题(我也经历过)可能是懒惰的GC.我不能说是100%,但是由于您的线程正在吃东西(繁重的工作),GC无法找到时间进行记忆.
即使在JVM旁边,它也表示它具有threaded-GC,但它可能无法在内存上工作并且有时(就像您所经历的那样)并行地释放数据.

One possible issue(as I experienced too) could be over-lazy GC. I cannot say it 100%, but since your thread is eating(heavy work), GC could not find a time to go for memory.
Even beside JVM indicates it has threaded-GC, but it may not work over the memory and release the data sometimes(as you experienced too) parallel.

您还可以检查您声明的哈希图,并确保所有需要添加和删除的数据均按预期完成.

You may also check the hashmap you stated, and make sure all data need to be added and removed are done as expected.

此外,您的globalCount也不是线程安全的,您可以改用AtomicInteger.

Also your globalCount is not thread safe, you may use AtomicInteger instead.

这篇关于JTextPane/HTMLEditorKit内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆