内存泄漏,以及runnable类的奇怪错误 - 如何正确处理对象?

时间:2015-07-27 02:43:34

标签: java multithreading swing memory-leaks runnable

我正在构建一个服务器应用程序来跟踪当前正在运行我最近构建的另一个应用程序的人数。这更像是为了测试自己,看看我能用语言做什么,几乎在任何时候我都能让事情发挥作用。但是,在应用程序上运行人工测试时,我遇到了一个严重的问题。生病了我的代码...

主要课程:

public class ServerGUIStatistics {

public static MainWindow mw = null;
private static int _port = 33672;

public static void main(String[] args) {
    mw = new MainWindow();
    new ServerGUIStatistics();
}

public ServerGUIStatistics(){
    ServerSocket listener = null;
    try{
        listener = new ServerSocket(_port, 100);
        while(true){
           System.out.println("Waiting for connection....");
           Socket connection = listener.accept(); 
           Connection nc = new Connection(connection);
        }
    }
    catch(Exception ex){
        mw.setConsoleOutput(ex.getMessage(), 2);// Prints to a console output window...
    }
}

我认为可燃烧的课程会导致这些问题。

class Connection implements Runnable {
private static int _totalConnections = 0;
private static long _totalConnectionsAllTime = 0;
private Socket socket = null;
private boolean stopFlag = true;

@Override
public void run(){
    while(stopFlag){
        stopFlag = false;
        ServerGUIStatistics.mw.setConsoleOutput("New connection from \"" + socket.getInetAddress().toString() + "\" on port " + socket.getPort(), 0);// Prints to a console output window... 
        ServerGUIStatistics.mw.updateConNumber(_totalConnections);
        ServerGUIStatistics.mw.updateConAllTimeNumber(_totalConnectionsAllTime);//Updates a field on the main window which displays the total number of users connected
        boolean stayAlive = true;
        try{
            socket.setSoTimeout(10000);
            BufferedInputStream connectionStatus = new BufferedInputStream(socket.getInputStream());
            do{
                int holder = connectionStatus.read();
                switch(holder){
                    case 1:
                        stayAlive = true;
                        break;
                    case 0:
                        stayAlive = false;
                        break;
                } 
            }
            while(stayAlive);
        }
        catch(IOException ex){
            ServerGUIStatistics.mw.setConsoleOutput(socket.getInetAddress().toString() + " " + ex.getMessage(), 2); // Prints to a console output window...
        }
        _totalConnections--;
        ServerGUIStatistics.mw.updateConNumber(_totalConnections); //Updates a field on the main window which displays the current number of users connected
        return;
    }
}

Connection(Socket soc){
    socket = soc;
    _totalConnections++;
    _totalConnectionsAllTime++;
    new Thread(this).start();
}

public static int getTotalConnections(){
    return _totalConnections;
}

这是摆动窗口(由netbeans生成):

import java.util.Date;
import java.text.*;

public class MainWindow extends javax.swing.JFrame {

/**
 * Creates new form MainWindow
 */
private Date curDate = null;
private SimpleDateFormat ft = new SimpleDateFormat ("MM/dd/yyyy @ hh:mm:ss");

public MainWindow() {
    initComponents();
    this.setVisible(true);
}
/**
 * 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() {
    bindingGroup = new org.jdesktop.beansbinding.BindingGroup();

    jTabbedPane1 = new javax.swing.JTabbedPane();
    jPanel1 = new javax.swing.JPanel();
    totalConnectionsOut = new javax.swing.JTextField();
    totalConnectionsAllTimeOut = new javax.swing.JTextField();
    jLabel1 = new javax.swing.JLabel();
    jLabel2 = new javax.swing.JLabel();
    jPanel3 = new javax.swing.JPanel();
    jScrollPane2 = new javax.swing.JScrollPane();
    consoleOutput = new javax.swing.JTextArea();

    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

    totalConnectionsOut.setFont(new java.awt.Font("Tahoma", 0, 58)); // NOI18N
    totalConnectionsOut.setHorizontalAlignment(javax.swing.JTextField.CENTER);
    totalConnectionsOut.setText("0");

    org.jdesktop.beansbinding.Binding binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, totalConnectionsOut, org.jdesktop.beansbinding.ELProperty.create("false"), totalConnectionsOut, org.jdesktop.beansbinding.BeanProperty.create("editable"));
    bindingGroup.addBinding(binding);

    totalConnectionsAllTimeOut.setFont(new java.awt.Font("Tahoma", 0, 58)); // NOI18N
    totalConnectionsAllTimeOut.setHorizontalAlignment(javax.swing.JTextField.CENTER);
    totalConnectionsAllTimeOut.setText("0");

    binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, totalConnectionsAllTimeOut, org.jdesktop.beansbinding.ELProperty.create("false"), totalConnectionsAllTimeOut, org.jdesktop.beansbinding.BeanProperty.create("editable"));
    bindingGroup.addBinding(binding);

    jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
    jLabel1.setText("Total Connections:");

    jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
    jLabel2.setText("Total Current Connections:");

    javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
    jPanel1.setLayout(jPanel1Layout);
    jPanel1Layout.setHorizontalGroup(
        jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(jPanel1Layout.createSequentialGroup()
            .addContainerGap()
            .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addComponent(totalConnectionsOut)
                .addComponent(totalConnectionsAllTimeOut, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 571, Short.MAX_VALUE)
                .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
            .addContainerGap())
    );
    jPanel1Layout.setVerticalGroup(
        jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(jPanel1Layout.createSequentialGroup()
            .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addGap(4, 4, 4)
            .addComponent(totalConnectionsOut, javax.swing.GroupLayout.PREFERRED_SIZE, 150, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, 34, Short.MAX_VALUE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(totalConnectionsAllTimeOut, javax.swing.GroupLayout.PREFERRED_SIZE, 150, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addContainerGap())
    );

    jTabbedPane1.addTab("Info", jPanel1);

    consoleOutput.setColumns(20);
    consoleOutput.setRows(5);

    binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, consoleOutput, org.jdesktop.beansbinding.ELProperty.create("false"), consoleOutput, org.jdesktop.beansbinding.BeanProperty.create("editable"));
    bindingGroup.addBinding(binding);

    jScrollPane2.setViewportView(consoleOutput);

    javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
    jPanel3.setLayout(jPanel3Layout);
    jPanel3Layout.setHorizontalGroup(
        jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(jPanel3Layout.createSequentialGroup()
            .addContainerGap()
            .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 571, Short.MAX_VALUE)
            .addContainerGap())
    );
    jPanel3Layout.setVerticalGroup(
        jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(jPanel3Layout.createSequentialGroup()
            .addContainerGap()
            .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 368, Short.MAX_VALUE)
            .addContainerGap())
    );

    jTabbedPane1.addTab("Console Output", jPanel3);

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addComponent(jTabbedPane1)
    );
    layout.setVerticalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addComponent(jTabbedPane1)
    );

    bindingGroup.bind();

    pack();
}// </editor-fold>                        

public void setConsoleOutput(String s, int i){
    String type = null;
    switch(i){
        case 0:
            type = "Info";
            break;
        case 1:
            type = "Error";
            break;
        case 2:
            type = "Exception";
            break;
        default:
            type = "Unknown";
            break;
    }
    curDate = new Date();
    String output = consoleOutput.getText() + "[" + ft.format(curDate) + "][" + type + "] " + s + "\n";
    consoleOutput.setText(output);
}

public void updateConNumber(int i){
    totalConnectionsOut.setText(Integer.toString(i));
}

public void updateConAllTimeNumber(long i){
    totalConnectionsAllTimeOut.setText(Long.toString(i));
}

// Variables declaration - do not modify                     
private javax.swing.JTextArea consoleOutput;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JPanel jPanel1;
private javax.swing.JPanel jPanel3;
private javax.swing.JScrollPane jScrollPane2;
private javax.swing.JTabbedPane jTabbedPane1;
private javax.swing.JTextField totalConnectionsAllTimeOut;
private javax.swing.JTextField totalConnectionsOut;
private org.jdesktop.beansbinding.BindingGroup bindingGroup;
// End of variables declaration                   

}

如果有什么看起来很奇怪,或者是不正确的话,那么我提醒你,我仍然是Java的新手,并且是制作这类应用程序的新手。

这是我做的人工测试:

class Main{
    public static void main(String[] args){
        while(true){
            try{
                Socket socket = new Socket("127.0.0.1", 33672);
                BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream());

                for(int i = 0; i < 5; i++){
                    out.write(1);
                    out.flush();
                    Thread.sleep(10);
                }

                out.write(0);
                out.flush();

                out.close();
                socket.close();
            }
            catch(Exception ex){System.out.println(ex);}
            System.out.println("Finished! Running again...");
        }
    }
}

正如您所看到的,它会快速连接以创建新线程,交换一些消息,然后断开然后重复。我把它保持运行直到它运行了大约1500次循环,它很快就完成了。但似乎没有我的内存使用量开始攀升...喜欢,很多。它开始冻结我的电脑了。正如我之前所说,它有1500个循环没有问题。在那时它使用了80到100兆字节的内存,但之后开始缓慢攀升。在2500个周期内,它使用了超过1GB的内存。到5000时,几乎所有系统内存都已饱和。

现在,我相信我解决这个问题的方法是在我完成它们时将Connection类的实例设置为null。但我不确定如何设置该程序。

有没有办法将对象从内部设置为null,还是有其他方法可以实现我应该使用的?

1 个答案:

答案 0 :(得分:2)

所以跳出来的东西......

在以下代码中......

while(stopFlag){
    stopFlag = false;
    ServerGUIStatistics.mw.setConsoleOutput("New connection from \"" + socket.getInetAddress().toString() + "\" on port " + socket.getPort(), 0);// Prints to a console output window... 
    ServerGUIStatistics.mw.updateConNumber(_totalConnections);
    ServerGUIStatistics.mw.updateConAllTimeNumber(_totalConnectionsAllTime);//Updates a field on the main window which displays the total number of users connected
    boolean stayAlive = true;
    try{
        socket.setSoTimeout(10000);
        BufferedInputStream connectionStatus = new BufferedInputStream(socket.getInputStream());
        do{
            int holder = connectionStatus.read();
            switch(holder){
                case 1:
                    stayAlive = true;
                    break;
                case 0:
                    stayAlive = false;
                    break;
            } 
        }
        while(stayAlive);
    }
    catch(IOException ex){
        ServerGUIStatistics.mw.setConsoleOutput(socket.getInetAddress().toString() + " " + ex.getMessage(), 2); // Prints to a console output window...
    }
    _totalConnections--;
    ServerGUIStatistics.mw.updateConNumber(_totalConnections); //Updates a field on the main window which displays the current number of users connected
    return;
}
  • 外部(while (stopFlag))毫无意义,因为你从内循环读取时从未测试它,一旦你退出内循环,你就会调用return
  • 您永远不会关闭BufferedInputStreamSocket。这两者中的任何一个都可以保持对不再使用的资源的强引用,防止类被垃圾收集

更好的解决方案“可能”看起来像......

ServerGUIStatistics.mw.setConsoleOutput("New connection from \"" + socket.getInetAddress().toString() + "\" on port " + socket.getPort(), 0);// Prints to a console output window... 
ServerGUIStatistics.mw.updateConNumber(_totalConnections);
ServerGUIStatistics.mw.updateConAllTimeNumber(_totalConnectionsAllTime);//Updates a field on the main window which displays the total number of users connected
boolean stayAlive = true;
try {
    socket.setSoTimeout(10000);
    try (BufferedInputStream connectionStatus = new BufferedInputStream(socket.getInputStream())) {
        do {
            System.out.println(number + " reading...");
            int holder = connectionStatus.read();
            System.out.println(number + " read " + holder + "...");
            switch (holder) {
                case 1:
                    stayAlive = true;
                    break;
                case 0:
                    stayAlive = false;
                    break;
            }
        } while (stayAlive);
    }
} catch (IOException ex) {
    ServerGUIStatistics.mw.setConsoleOutput(socket.getInetAddress().toString() + " " + ex.getMessage(), 2); // Prints to a console output window...
} finally {
    System.out.println(number + " exiting");
    _totalConnections--;
    try {
        socket.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    socket = null;
    ServerGUIStatistics.mw.updateConNumber(_totalConnections); //Updates a field on the main window which displays the current number of users connected
}

请注意使用The try-with-resources Statement确保代码存在BufferedInputStream块时try已关闭。

注意使用finally来确保执行代码块,而不管退出try块的方式如何。这保证了代码的执行。

接下来,我把注意力转向......

public void setConsoleOutput(String s, int i){
    String type = null;
    switch(i){
        case 0:
            type = "Info";
            break;
        case 1:
            type = "Error";
            break;
        case 2:
            type = "Exception";
            break;
        default:
            type = "Unknown";
            break;
    }
    curDate = new Date();
    String output = consoleOutput.getText() + "[" + ft.format(curDate) + "][" + type + "] " + s + "\n";
    consoleOutput.setText(output);
}

暂时忽略违反Swing的单个线程,直接的问题是你在这个方法的每次运行中创建一个新的Date对象,这个对象被称为1000次,但是{{ 1}} object本身是一个实例字段,只能添加问题。

但真正令人担忧的是......

Date

虽然编译器可以很好地进行优化,但String output = consoleOutput.getText() + "[" + ft.format(curDate) + "][" + type + "] " + s + "\n"; 方法会创建一个新的consoleOutput.getText(),其中包含文本区域的内容,然后您可以将其用于应用{{1}使用String

这非常低效,因为基础JTextArea每次都将setText数组转换为Documentchar转换回String数组...

相反,你可以相对简单地解决这两个问题......

String

只需使用char并将其传递给public void setConsoleOutput(String s, int i) { String type = null; switch (i) { case 0: type = "Info"; break; case 1: type = "Error"; break; case 2: type = "Exception"; break; default: type = "Unknown"; break; } String output = "[" + ft.format(System.currentTimeMillis()) + "][" + type + "] " + s + "\n"; consoleOutput.append(output); } ,而不是创建新的Date对象。接下来,只需使用System.currentTimeMillis()将文本附加到SimpleDateFormatter的末尾,这将减少至少一个(如果不是更多)创建的临时对象的数量,从而减少GC周期的开销而不是留下一堆堆积在堆里的短生物......

我使用append作为循环终止符,通过分析器(在Netbeans中)运行此更新的代码,并且直到大约第4000个周期左右才看到使用的堆大小超过10mb ...

现在,由于JTextArea的性质,我希望随着时间的推移会看到内存使用量略有增加,因为它需要将所有文本保留在内存中(以及计算文本的相关开销)布局)

有关Swing和线程

的更多详细信息,请查看Concurrency in Swing