A especificação de Java EE define algumas limitações para o desenvolvimento de aplicações dentro da plataforma, como por exemplo a abertura de server sockets, utilização do mecanismo de reflection e a criação de threads. Esse post tenta compreender melhor o porque desse último.
O ambiente do servidor de aplicações e a JVM já criam uma grande quantidade de threads, necessárias ao ambiente, no caso do servidor de aplicações podemos citar como exemplo:
- Threads para a comunicação com o Conteiner Web
- Threads para a comunicação com o Conteiner EJB
- Threads para a comunicação com o servidor de mensagens
- Threads auxiliares ao ambiente, para rotinas de limpeza, comunicação, manutenção e monitoramento do ambiente
- Threads da JVM para a coleta de lixo
Em um ambiente simples, com apenas uma instância de servidor de aplicações e algumas aplicações em execução, é comum que o processo java do servidor de aplicações tenha algo em torno de 100 threads em execução.
Essas threads pertecem ao processo java iniciado pela JVM e ocupam espaço de memória nativa diferente do espaço alocado pelo heap, que é a memória alocada pela JVM para trabalhar com os objetos.
Para alterar a quantidade de memória utilizada por uma thread utilizamos o modificador da JVM –Xss, lembrando que essas threads tem a sua disposição ciclos de processamento da CPU. Isso siginifica que uma thread que você inicia dentro da sua aplicação irá consumir recursos que podem ser preciosos para a JVM.
Ocupar o espaço de memória nativa acaba gerando um efeito interessante, no caso de iniciar muitas threads em uma aplicação em execução dentro do servidor de aplicações, a memória necessária a essas threads pode começar a ser empurrada para o espaco de memoria virtual, i.e, a área de swap do sistema operacional. Esse comportamento pode ocasionar desde um comportamento estranho na aplicação, como tempo um alto tempo de resposta, problemas na renderização de páginas WEB, ciclos de GC extremamente lentos e a sensação de que a JVM, e por consequência a aplicação, travou. Em último caso, pode ser lançado um java.lang.OutOfMemoryError, sendo que é possível que uma rápida visualização do processo no sistema operacional demonstre que a JVM não alocou todo o espaço disponível para ela.
Por conta disso, devemos analisar e planejar bem a nossa aplicação, aliás tenho percebido que essa parte tem sido bastante negligenciada pelos times de desenvolvimento atualmente (esse vai ser tópico para um outro post). Caso realmente seja percebida a necessidade de se trabalhar com threads dentro da aplicação, que seja algo bem calculado, coeso, controlado e bem implementado. Essa mesma observação vale para quando fazemos uma configuração e fine tuning do ambiente do servidor de aplicações.
Um abraço, Marcelo Sousa Ancelmo