【问题标题】:Java memory leak from thread pool线程池中的 Java 内存泄漏
【发布时间】:2015-05-20 15:49:55
【问题描述】:

我正在尝试构建服务器,但线程池的使用引入了内存泄漏,导致我的堆溢出。

我的服务器从主线程创建了两个主线程,Acceptor 和 Consumer。我是 JProfiler 的新手,但它似乎非常清楚地将我指向下面的代码(Acceptor)作为罪魁祸首。

Acceptor 线程接受来自客户端的新连接。在接受调用后,线程池中的新线程用于处理连接。

public class AcceptorThread implements Runnable{
LinkedBlockingQueue<LinkedList<Log_Message>> queue = null;
public AcceptorThread(LinkedBlockingQueue<LinkedList<Log_Message>> queue){
    this.queue = queue;
}

@Override
public void run() {
    ExecutorService acceptors = Executors.newCachedThreadPool();
    ServerSocket socket = null;
    try {
        socket = new ServerSocket(44431);
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    while(!init_LogServer.isStopped()){
        Socket connectionSocket = null;
        try {
            connectionSocket = socket.accept();
        } catch (IOException e) {
            e.printStackTrace();
        }
        init_LogServer.addList();
        System.out.println("Accepted New List: " + init_LogServer.getListCount());
        Runnable worker = new List_Acceptor(connectionSocket, queue);
        acceptors.execute(worker);
    }
}

}

线程池中的新线程接收到一个对象的链表,这个链表被加入到队列中,线程就完成了。我的理解是,在完成下面的代码后,线程将终止,下面的对象将被 GC 收集,线程将重新进入线程池以供重用。我看不到任何明显的小内存泄漏,所以我猜测会发生什么情况是 GC 永远不会收集下面的类导致堆构建,对吗?将接收到的列表添加到队列后,如何销毁以下对象?

public class List_Acceptor implements Runnable{
Socket socket = null;
private LinkedList<Log_Message> lmList = null;
private LinkedBlockingQueue<LinkedList<Log_Message>> queue = null;
private String dumpMessage = null;

public List_Acceptor(Socket socket, LinkedBlockingQueue<LinkedList<Log_Message>> queue){
    this.queue = queue;
    this.socket = socket;
}

@Override
public void run() {
    ObjectInputStream input = null;
    try {
        input = new ObjectInputStream(socket.getInputStream());
    } catch (IOException e3) {
        // TODO Auto-generated catch block
        e3.printStackTrace();
    }
    Object received = null;
    try {
        received = input.readObject();
    } catch (ClassNotFoundException | IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    try {
        lmList = (LinkedList<Log_Message>) received;
    } catch (Exception e1) {
        //e1.printStackTrace();
        System.out.println("Received DumpLog");
        dumpMessage = (String) received;
        System.out.println("Dump: "+dumpMessage);
        init_LogServer.stop();
        e1.printStackTrace();
    }

    //close socket
    try {
        socket.close();
        input.close();
    } catch (IOException e2) {e2.printStackTrace();}

    try {
        queue.put(lmList);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

}

}

编辑:我了解到,当一个对象/类通过线程实例化时,就像我对线程池所做的那样,GC 被限制清理该对象。这似乎是内存泄漏的可能原因,并且可能是我的问题。考虑到这一点,明天我将重新设计,以便 AcceptorTread 执行将接收到的列表放入队列的必要步骤,它应该会更慢,但会防止堆问题。那有意义吗?

【问题讨论】:

    标签: java multithreading memory-leaks garbage-collection jprofiler


    【解决方案1】:

    关于您上次的编辑:当前线程不会使您创建的所有 List_Acceptor 对象保持活动状态(顺便说一句,Java 命名约定是 ListAcceptor)。

    JProfiler 中的分配热点视图只是告诉您分配了仍在堆上的对象。这并不意味着这些对象不能被 GC。

    要分析哪些对象实际上是强引用的,请转到 JProfiler 中的“Heap walker”。然后选择所有List_Acceptor 对象并转到“传入引用”视图。使用单个对象,单击“显示 GC 根路径”。然后你会看到一个阻止对象被GCed的引用链。

    使用“累积传入引用”视图,您可以检查是否所有 List_Acceptor 对象都是这种情况。

    【讨论】:

      【解决方案2】:

      如果您的线程在完成请求后保留对对象的引用,只是为了在新请求到来时覆盖它们,那么原始对象将“固定”到内存中,直到该线程再次变为活动状态。这些固定的对象会被多次复制并占用 ram,只是稍后才会释放。

      循环的工作线程应该在等待另一个对象工作之前清除所有的对象引用。

      【讨论】:

        猜你喜欢
        • 2017-12-07
        • 2012-05-12
        • 2016-12-09
        • 2012-02-17
        • 2018-12-18
        • 2014-12-30
        • 2015-11-17
        • 2014-10-29
        • 2013-12-18
        相关资源
        最近更新 更多