Diagnóstico de Congelamento do Servidor Tomcat

Cenário do Problema

Em ambientes de produção, é possível encontrar situações onde o servidor Tomcat não encerra com erro e os logs não apresentam exceções, no entanto, as requisições HTTP ficam sem resposta. Quando isso ocorre, o Tomcat apresenta um comportamento de congelamento. Este texto aborda os procedimentos para diagnosticar tal problema.

Ambiente de Referência

Componente Versão
Tomcat 7.0
JDK 1.6
CentOS 6

Possíveis Causas

O congelamento do Tomcat pode ser originado por diversos fatores. A seguir, listam-se as causas mais comuns encontradas na prática:

  1. Deadlock em estruturas HashMap.
  2. Vazamento de memória (Memory Leak).
  3. Acúmulo excessivo de conexões no estado CLOSE_WAIT.

Passos para Diagnóstico

Ao identificar o congelamento, evite reiniciar o serviço imediatamente. Realize uma análise sistematizada primeiro.

1. Análise do Garbage Collector (GC)

Para verificar o estado do coletor de lixo e a utilização da memória heap, execute o comando:

jmap -heap <pid> > status_heap.txt

Este comando gera um relatório contendo detalhes sobre a configuração do heap, algoritmo de GC utilizado e o consumo atual de memória. Um exemplo de saída pode indicar alta utilização da geração antiga (Old Generation) e da memória Perm Gen, sugerindo possíveis problemas de retenção de objetos.

Outra ferramenta útil é o jstat, que fornece estatísticas de GC em tempo real:

jstat -gcutil <pid> <intervalo_ms> <amostras>

Por exemplo, jstat -gcutil 12345 250 7 coleta 7 amostras a cada 250 milissegundos. Os campos como O (Old Generation), P (Perm Gen), FGC (contagem de Full GC) e FGCT (tempo total de Full GC) são indicadores críticos. Valores elevados de FGC e FGCT combinados com alta utilização de memória antiga apontam para necessidade de coleta de lizo completa e frequente, o que pode paralisar a aplicação.

2. Investigação de Vazamento de Memória

Vazamentos frequentemente surgem após longos períodos de execução ou mudanças no código. Para diagnóstico em ambiente de produção, é recomendável gerar um dump completo do heap da JVM:

jmap -dump:format=b,file=dump_heap.bin <pid>

O arquivo gerado pode ser volumoso (gigabytes). Após comprimi-lo, utilize ferramentas de análise como o JProfiler ou o Eclipse MAT para examinar o dump. O objetivo é identificar objetos dominantes ou coleções que crescem continuamente.

Em um caso real, a análise revelou uma instância de um plugin de monitoramento (Tingyun) consumindo 1487 MB de um heap total de 3072 MB. Este plugin havia sido desativado incorretamente no passado, mas continuava tentando se conectar a um servidor já desativado, causando retenção de memória e GC constante. A remoção definitiva do plugin resolveu o congelamento frequente.

3. Detecção de Deadlock em HashMap

Para inspecionar threads em tempo real sem interromper a aplicação, utilize o Arthas (ferramenta da Alibaba). Conecte-se ao processo Java e execute o comando thread.

É possível observar threads permanentemente no estado BLOCKED ou WAITING, muitas vezes envolvidas em operações de HashMap. Em versões antigas do JDK, HashMap não era seguro para uso concorrente, e a rehashing em múltiplas threads poderia levar a loops infinitos. Este deadlock, mesmo sendo esporádico, acumula conexões HTTP ao longo do tempo até esgotar o pool de threads do Tomcat, resultando no congelamento.

4. Verificação de Conexões CLOSE_WAIT

Um grande número de conexões TCP no estado CLOSE_WAIT indica que o servidor está fechando a conexão, mas o processo da aplicação não liberou o recurso do socket. Para monitorar o estado das conexões:

netstat -n | awk '/^tcp/ {++estados[$NF]} END {for(estado in estados) print estado, estados[estado]}'

Uma saída mostrando centenas ou milhares de entradas CLOSE_WAIT é anormal. A solução padrão é configurar o conector HTTP do Tomcat para não manter conexões persistentes (keep-alive). No arquivo server.xml, adicione ou modifique o atributo keepAliveTimeout:

<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           keepAliveTimeout="0"
           redirectPort="8443" />

Definir keepAliveTimeout="0" faz com que o servidor feche a conexão imediatamente após a resposta, evitando o acúmulo de sockets no estado CLOSE_WAIT.

Resumo da Análise

O diagnóstico requer o uso combinado de ferramentas de linha de comando da JDK (jmap, jstat), analisadores de heap (JProfiler) e instrumentação em tempo real (Arthas). A identificação correta da causa — seja memória, deadlock ou gerenciamento de conexões — é essencial para aplicar a correção apropriada no ambiente de produção.

Tags: tomcat JVM Garbage Collection Memory Leak Deadlock

Publicado em 6-27 15:59