Утечка памяти Java из пула потоков

Я пытаюсь построить сервер, но использование пула потоков привело к утечке памяти, которая приводит к переполнению моей кучи.

Мой сервер создает два основных потока из основного потока: 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);
    }
}

}

Новый поток из пула потоков получает связанный список объектов, этот список добавляется в очередь и поток завершается. Насколько я понимаю, после завершения приведенного ниже кода поток завершится, указанный ниже объект будет собран сборщиком мусора, и поток повторно войдет в пул потоков для повторного использования. Я не вижу каких-либо очевидных небольших утечек памяти, поэтому я предполагаю, что происходит то, что приведенный ниже класс никогда не собирается сборщиком мусора, что приводит к созданию кучи, это правильно? Как я могу уничтожить указанный ниже объект после добавления полученного списка в очередь?

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 выполнял необходимые шаги для помещения полученного списка в очередь, это должно быть медленнее, но предотвратит проблему с кучей. Имеет ли это смысл?


person Nick    schedule 18.03.2015    source источник


Ответы (2)


Что касается вашего последнего редактирования: текущий поток не будет поддерживать все ваши созданные объекты List_Acceptor (кстати, соглашение об именах Java - ListAcceptor).

Представление горячих точек распределения в JProfiler просто говорит вам, были ли выделены объекты, которые все еще находятся в куче. Это не означает, что эти объекты не могут быть проверены GCed.

Для анализа того, на какие объекты на самом деле строго ссылаются, перейдите к «Проходчику кучи» в JProfiler. Затем выберите все List_Acceptor объектов и перейдите в представление «Входящие ссылки». С одним объектом нажмите «Показать путь к корню GC». Затем вы увидите цепочку ссылок, которая предотвращает GCed объекта.

В представлении «Накопленная входящая ссылка» вы можете проверить, верно ли это для всех List_Acceptor объектов.

person Ingo Kegel    schedule 18.03.2015

Если ваши потоки сохраняют ссылки на объекты после завершения запроса только для того, чтобы перезаписать их при поступлении нового запроса, исходные объекты «закрепляются» в памяти до тех пор, пока этот поток снова не станет активным. Эти закрепленные объекты копируются много раз и занимают оперативную память только для того, чтобы быть освобожденными позже.

Рабочие потоки в этом цикле всегда должны очищать все свои ссылки на объекты, прежде чем ждать другого объекта, над которым можно работать.

person Brian White    schedule 20.03.2015