Multi Thread Server - uso de memória

14 respostas
E

Boa tarde pessoal, criei um servidor de socket que suporta multiplos clientes. Está funcionando direitinho.
O problema é que a cada conexão recebida, a memória usada pelo servidor aumenta (É possivel ver no gerenciador de tarefas).
É como se a thread nunca morresse…

Tirei fora a parte do socket, deixei apenas o servidor recebendo a conexão e iniciando uma Thread (vazia)
e adivinha só, o problema continua… a memória gasta é menor, mas ainda assim continua aumentando.

Esse comportamento é normal ? Há uma forma de evitar isso ?
Pois chega uma hora que ele atinge o limite, e para de receber conexões.

segue código do multi thread.

public class MiddleServer {

    public static void main(String[] args) throws IOException, InterruptedException {
	
        try {
            ServerSocket listener = new ServerSocket(4444);
               while(true) {
                listener.accept();
                Thread t = new Thread();
                t.start();
            }
        } catch (IOException ioe) {
            System.err.println("Could not listen on port" + ioe);
            System.exit(-1);
        }
    }
}

agradeço a ajuda.

14 Respostas

V

Esse comportamento não é normal. Para descobrir gargalos e problemas de performance, use um profiler. Eu recomendaria usar o do Netbeans ou o Visual VM, que acompanha o próprio Java.

V

Nesse caso, talvez haja problema pois você nunca está dando close() nos sockets recebidos no accept.

E

O close fica na outra classe… segue codigo completo:

Multi Thread:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
 
public class MiddleServer {

    public static void main(String[] args) throws IOException, InterruptedException {
	
        try {
            ServerSocket listener = new ServerSocket(4444);
            Socket server;
            while(true) {
                server = listener.accept();
                KKMultiServerThread conn = new KKMultiServerThread(server);
                Thread t = new Thread(conn);
                t.start();
            }
        } catch (IOException ioe) {
            System.err.println("Could not listen on port" + ioe);
            System.exit(-1);
        }
    }
}

Runnable:

import java.io.IOException;
import java.net.Socket;

public class KKMultiServerThread implements Runnable {
	
	private Socket socket = null;
	
	public KKMultiServerThread(Socket socket) {
		this.socket = socket;
	}

	public void run() {
            try {
                this.socket.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
	}
}

Quanto ao profile, eu tentei utilizar seguindo esse tutorial.
Parece que realmente tem algo errado, mas não consigo entender o que. Ele aponta para umas classes nativas.
:?

E

Conselho: em vez de ficar criando threads à toa, use um pool de threads. Procure por ThreadPoolExecutor:

http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html

E

entanglement:
Conselho: em vez de ficar criando threads à toa, use um pool de threads. Procure por ThreadPoolExecutor:

http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html

vou verificar.


fiz a alteração utilizando ThreadPool

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class MiddleServer {

    public static void main(String[] args) throws IOException, InterruptedException {
	
        try {
            ExecutorService exec = Executors.newFixedThreadPool(50);            
            ServerSocket listener = new ServerSocket(Constantes.PORTA_CRIPTO);
            Socket server;
            boolean quebraLoop = false;
            while(!quebraLoop) {
                server = listener.accept();
                exec.execute(new KKMultiServerThread(server));
            }
        } catch (IOException ioe) {
            System.err.println("Could not listen on port" + ioe);
            System.exit(-1);
        }
    }
}

mas o problema continua…
no gerenciador de memória, a aplicação consome 9.032 kb.
e vai aumentando conforme vai chegando conexões… depois de umas 10 conexões chega a estar com 10.228 kb
eu sei que é coisa mínima, mas com o tempo chega ao limite e trava tudo.
pra quebrar o galho, estou reiniciando a aplicação toda segunda-feira.

E

Ou seja, você descobriu que o problema de uso de memória não é com threads.

Só por curiosidade, você está usando ObjectInputStream/ObjectOutputStream com sockets?

Costuma dar muito problema de vazamento de memória, a menos que você saiba o que está fazendo (ou seja, leia o código da implementação dessas classes, e use o método http://docs.oracle.com/javase/6/docs/api/java/io/ObjectOutputStream.html#reset() de vez em quando.

E

entanglement:
Ou seja, você descobriu que o problema de uso de memória não é com threads.

Só por curiosidade, você está usando ObjectInputStream/ObjectOutputStream com sockets?

Costuma dar muito problema de vazamento de memória, a menos que você saiba o que está fazendo (ou seja, leia o código da implementação dessas classes, e use o método http://docs.oracle.com/javase/6/docs/api/java/io/ObjectOutputStream.html#reset() de vez em quando.

Olá entanglement

Dentro do runnable eu uso sim

nos = socket.getOutputStream();
			in = new BufferedReader(
					new InputStreamReader(
							socket.getInputStream()));

mas nesse exemplo não coloquei, e o problema continua.
será que você poderia testar o código que postei antes do ThreadPool ? são 2 clases apenas.
eu executo o server, que fica ouvindo a porta 4444, e acesso essa porta via browser. deixo o gerenciador de tarefas aberto e percebo o consumo de memória aumentando conforme as conexões. eu sou realmente muito iniciante em java, por isso, não creio que eu vá conseguir descobrir se há algo errado ou se esse é um comportamento padrão.

E

Não estou me referindo a BufferedReader e sim a ObjectInputStream / ObjectOutputStream (a menos que você tenha misturado o BufferedReader/PrintWriter com ObjectInputStream/ObjectOutputStream, o que não é nem um pouco saudável :slight_smile: )

E

entanglement:
Não estou me referindo a BufferedReader e sim a ObjectInputStream / ObjectOutputStream (a menos que você tenha misturado o BufferedReader/PrintWriter com ObjectInputStream/ObjectOutputStream, o que não é nem um pouco saudável :slight_smile: )

desculpe minha falta de atenção…
não utilizo ObjectInputStream / ObjectOutputStream.
:slight_smile:

E

Ufa, um problema a menos.

Pois bem, o aumento da memória, no seu caso, só pode ser diagnosticado com uma ferramenta (como o jhat ou o Eclipse Memory Analyzer, http://www.eclipse.org/mat/ )

O que você faz, normalmente:

a) Captura um instantâneo da memória consumida por sua aplicação depois que você criou uma 2 ou 3 conexões
b) Captura outro instantâneo depois que você criou mais outras conexões (digamos umas 1000) mas depois você as encerrou
c) Compara os instantãneos e vê que objetos estão ficando “presos”

E

Eu cheguei a fazer isso usando o VisualVM, nativo do JDK.
Não consegui nenhuma pista… vou testar com o Eclipse Memory Analyzer.

E

Moçada, fiz o teste o com o Eclipse Analyser, e nada !
Copiei e colei o exemplo do site da Oracle e coloquei pra rodar (o do fim da página):

http://docs.oracle.com/javase/tutorial/networking/sockets/clientServer.html

E adivinhem só, o mesmo problema !!!

Por isso, volto a questionar… esse é um comportamento comum do java ?
Ou os caras da Oracle teriam a capacidade de postar um exemplo com Memory Leak ?

qualquer ajuda será bem-vinda.

E

Sr. Edinho,

Na prática, nunca se inicia uma thread para cada conexão.

Usa-se um pool de threads (o Java já tem um pool pronto - procure no pacote java.util.concurrent um troço chamado http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html ).

Uma vantagem de usar um pool com tamanho fixo é que é trivial rejeitar uma conexão se um limite for alcançado :slight_smile:

EDIT - não olhei que você já estava usando um pool de threads :frowning:

De qualquer forma, sempre que você receber uma conexão e a encapsular em um BufferedAlgumaCoisa, não se esquecer de fechar o BufferedAlgumaCoisa também - só um BufferedAlgumaCoisa já reserva um buffer de 8KB por default, que só é liberado quando ele é explicitamente fechado.

E

Muitos dos exemplos do Java Tutorial não são para serem usados no esquema “copiar e colar em um sistema em produção”.
Muitas vezes eles não são “exemplos de melhores práticas”.
São apenas “exemplos de como uma determinada coisa pode ser usada”, ou seja, você tem de entender o que está sendo feito, para você poder fazer algo melhor.
Note que eles:
a) Não fazem tratamento completo de exceções (senão o exemplo ficaria muito poluído com tratamento de exceções etc.)
b) Usam certas coisas (como DefaultTableModel) que só são usados em exemplos, não em sistemas reais.

Criado 10 de abril de 2013
Ultima resposta 5 de jun. de 2013
Respostas 14
Participantes 3