A análise de thread dump é o caminho mais curto entre "a aplicação está estranha" e "o problema é este método, nesta thread, disputando este lock". Quando a JVM trava, fica lenta ou queima CPU sem entregar resposta, o thread dump mostra — em texto puro, sem agente, sem restart — o que cada thread estava fazendo naquele instante. É o exame mais barato e mais informativo do arsenal de quem opera Java em produção.
Este guia cobre o ciclo completo: o que é (e o que não é) um thread dump, todas as formas de gerar um, a anatomia do arquivo linha a linha, o significado real de cada estado de thread, um roteiro prático de análise em 5 passos e os padrões de problema que você vai encontrar com mais frequência — de deadlock a pinning de virtual threads.
O que é um thread dump — e o que ele não é
Um thread dump é um snapshot textual de todas as threads de uma JVM em um instante específico. Para cada thread ele registra nome, identificadores, prioridade, estado, pilha de chamadas completa e os locks que ela detém ou aguarda. A coleta custa microssegundos, não pausa a aplicação de forma perceptível e não exige nenhuma configuração prévia — qualquer JVM sabe se autodescrever assim desde sempre.
O que ele não é: um perfil de CPU (não diz quanto tempo cada método consumiu, só onde a thread estava no instante da foto), um histórico (não mostra o que aconteceu antes ou depois) nem um retrato da memória. Por isso a técnica central deste guia é comparar múltiplos dumps: o movimento entre fotos é que conta a história.
Thread dump vs heap dump vs core dump
Os três são confundidos com frequência, mas respondem a perguntas diferentes. O thread dump descreve execução: quem está rodando, esperando ou bloqueado — quilobytes de texto, seguro de coletar a qualquer momento. O heap dump descreve memória: todos os objetos vivos e suas referências — centenas de MB ou GB, pausa a JVM durante a captura e é o exame certo para vazamento de memória e OutOfMemoryError. O core dump é a imagem binária do processo inteiro no nível do sistema operacional, incluindo memória nativa — usado quando a própria JVM morre (crash, SIGSEGV), analisado com ferramentas como jhsdb. Para travamento, lentidão e CPU alta, o thread dump é quase sempre o ponto de partida.
Como gerar um thread dump
Todos os métodos abaixo produzem o dump sem reiniciar nem derrubar a aplicação. Escolha pelo acesso que você tem: shell no host, apenas HTTP, ou só o console.
jstack
jstack -l <pid> > dump-$(date +%H%M%S).txt
A ferramenta clássica, distribuída com o JDK. A flag -linclui a seção de locks java.util.concurrent (os "ownable synchronizers"), essencial para investigar contenção comReentrantLock. Precisa rodar com o mesmo usuário do processo da JVM. Descubra o pid comjps ou ps.
jcmd
jcmd <pid> Thread.print
O canivete suíço moderno do JDK — mesma saída do jstack e recomendado pela Oracle como substituto. Em JVMs com virtual threads, prefirajcmd <pid> Thread.dump_to_file -format=json arquivo.json, que inclui as virtual threads desmontadas — o formato clássico mostra apenas as platform/carrier threads.
kill -3 (SIGQUIT)
kill -3 <pid>
Em Linux/Unix, a JVM intercepta o SIGQUIT e escreve o dump no stdout do próprio processo — não no seu terminal. Procure o resultado no log da aplicação ou emkubectl logs / docker logs. É a opção de sobrevivência em containers enxutos sem JDK instalado, e não mata o processo, apesar do nome.
Ctrl+Break (Windows)
Em Windows, com a JVM rodando em um console, pressioneCtrl+Break nessa janela: o dump é impresso no próprio console. É o equivalente do kill -3 — para serviços sem console, use jstack ou jcmd.
Spring Boot Actuator
GET /actuator/threaddump
Se a aplicação expõe o Actuator, esse endpoint devolve o dump via HTTP — sem shell no host. Por padrão retorna JSON; com header Accept: text/plainvem o formato clássico. Exige liberar o endpoint emmanagement.endpoints.web.exposure.include e, por revelar internals, deve ficar atrás de autenticação.
VisualVM e JDK Mission Control
Ferramentas gráficas do ecossistema: conecte no processo local (ou remoto via JMX) e capture o dump com um clique na aba de threads. Ótimas na máquina de desenvolvimento; em produção normalmente não há GUI nem porta JMX exposta, então os métodos de linha de comando dominam.
Seja qual for o método, a boa prática é uma só: colete 3 dumps com ~10 segundos de intervalo. Um dump é foto; três são filme. Thread parada na mesma pilha nos três dumps está travada — thread que muda de frame está apenas trabalhando.
Anatomia de um thread dump
Um dump HotSpot tem quatro partes: o cabeçalho da JVM, um bloco por thread, as seções de locks e, quando existe, o veredito de deadlock no final. Veja um trecho real:
Full thread dump OpenJDK 64-Bit Server VM (21.0.2+13 mixed mode, sharing):
"http-nio-8080-exec-12" #87 daemon prio=5 os_prio=0 cpu=1042.11ms elapsed=3608.42s
tid=0x00007f2c8c1d8000 nid=0x4e21 waiting for monitor entry [0x00007f2c5b3f6000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.exemplo.estoque.ReservaService.reservar(ReservaService.java:61)
- waiting to lock <0x000000071a2b3c48> (a java.lang.Object)
at com.exemplo.api.PedidoController.criar(PedidoController.java:39)
Locked ownable synchronizers:
- <0x000000071b9d0a10> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)- Cabeçalho da JVM — identifica implementação e versão (
Full thread dump OpenJDK...). É por ele que ferramentas detectam o formato (HotSpot, OpenJ9, Zing, GraalVM). - Linha da thread — nome entre aspas (aqui, worker 12 do pool HTTP do Tomcat), número da thread (
#87), flagdaemon, prioridades (prio,os_prio), tempo de CPU acumulado,tid(endereço interno da JVM) enid— o ID nativo da thread no SO, em hexadecimal. O nid é ouro em investigação de CPU: cruze com a saída detop -H -p <pid>para descobrir qual thread Java está queimando processador. - Estado e pilha — a linha
java.lang.Thread.Statetraz o estado oficial, seguida da pilha de chamadas do frame mais recente para o mais antigo. - Seção de locks — entremeadas na pilha aparecem as linhas
- locked <endereço>e- waiting to lock <endereço>. O endereço é o identificador do monitor: threads que citam o mesmo endereço estão disputando o mesmo lock. Com-l, o blocoLocked ownable synchronizerslista os locksjava.util.concurrentdetidos.
Estados de thread: RUNNABLE, BLOCKED, WAITING e TIMED_WAITING
Todo diagnóstico começa classificando as threads por estado — e sabendo o que cada estado realmente significa (e onde ele engana).
- RUNNABLE — executando código ou pronta para executar assim que o SO der CPU. A pegadinha clássica: uma thread parada em IO de socket (
socketRead0,SocketDispatcher.read) aparece como RUNNABLE mesmo estando completamente parada esperando bytes da rede — a JVM não enxerga o bloqueio que acontece no nível do sistema operacional. RUNNABLE em pilha de rede quase nunca é consumo de CPU; RUNNABLE em loop de parsing, regex ou serialização quase sempre é. - BLOCKED — quer entrar em um bloco
synchronizedcujo monitor está com outra thread. É sempre contenção: a linhawaiting to lockdiz qual monitor, e alguma outra thread no dump tem o- lockedcorrespondente. Muitas BLOCKED no mesmo endereço = gargalo de sincronização. - WAITING — chamou
Object.wait(),LockSupport.park()ouThread.join()sem timeout e depende de outra thread para acordar. É o estado natural de workers ociosos esperando tarefa na fila do pool — mas é suspeito quando aparece no meio de uma pilha de código de negócio (alguém esperando umFutureque nunca completa, por exemplo). - TIMED_WAITING — igual ao WAITING, mas com prazo:
sleep(ms),wait(ms),poll(timeout),parkNanos. Estado normal de schedulers, health checks e consumidores de fila com timeout.
Regra de bolso: estado sozinho não condena nem absolve uma thread. Estado + pilha + a comparação entre dumps consecutivos é que formam o diagnóstico.
Como analisar na prática: roteiro em 5 passos
- Agrupe por pool e por estado. Conte as threads por prefixo de nome (
http-nio-*-exec-*,ForkJoinPool, pools customizados) e por estado. Só essa tabela já responde perguntas grandes: o pool HTTP está saturado? Há um número anormal de threads no total? - Procure deadlock declarado. Busque a string
Found one Java-level deadlockno fim do dump. Quando ela existe, a JVM já cruzou os locks por você e o diagnóstico está pronto. - Compare os 3 dumps procurando threads paradas no mesmo frame. Thread com pilha idêntica nos três dumps está travada, não lenta. É o filtro mais poderoso para separar as 12 threads que importam das 800 que estão só trabalhando.
- Mapeie os locks disputados. Para cada thread BLOCKED, siga o endereço do
waiting to lockaté a thread com o- lockedcorrespondente e pergunte: o que a dona do lock está fazendo? Se ela está em IO externo, você achou a raiz da fila. - Correlacione com o sintoma. CPU alta pede foco nas RUNNABLE de computação; timeout de requisições pede foco no pool HTTP; crescimento de memória com threads pede contagem entre dumps. O dump confirma ou refuta a hipótese — nunca comece pela conclusão.
Padrões de problema
Com o roteiro na mão, estes são os padrões que você vai encontrar em produção, do mais explícito ao mais sutil.
Deadlock
Duas ou mais threads esperando locks em ordem cruzada — nenhuma avança nunca. O HotSpot detecta o ciclo de monitores e imprime Found one Java-level deadlock com as threads e locks envolvidos. Deadlocks comReentrantLock via lock()também são declarados quando o dump é coletado com -l. Dedicamos um guia inteiro ao tema em como detectar e resolver deadlocks Java.
Pool exhaustion
Todas as threads workers do pool ocupadas — nenhuma disponível para novas requisições, e a aplicação "trava" sem nenhum erro no log. No Tomcat, o sinal é todas ashttp-nio-*-exec-* em RUNNABLE de IO, BLOCKED ou WAITING em chamadas externas. A causa típica é uma dependência lenta sem timeout contaminando o pool inteiro. Tratamos o caso Spring Boot em detalhe em threads presas no Spring Boot.
CPU spike
Threads RUNNABLE com pilha em computação pura — loop sem condição de saída, regex catastrófica, serialização de grafos de objetos enormes, hash flooding. Confirme cruzando o nid com top -H: a thread que aparece no topo do consumo é a culpada. Se a mesma pilha de computação se repete nos 3 dumps, o loop é real.
Thread leak
A contagem total de threads cresce entre dumps e nunca volta. Quase sempre é umExecutorService criado por requisição e nunca fechado, ounew Thread(...) sem pool. O sintoma terminal éOutOfMemoryError: unable to create native thread. A assinatura no dump: dezenas de threads com nomes sequenciais (pool-847-thread-1) todas ociosas.
Lock contention
Muitas threads BLOCKED apontando para o mesmo endereço de monitor. Não trava a aplicação como um deadlock, mas serializa o que deveria ser paralelo — o throughput despenca e o P99 explode. A correção passa por reduzir o escopo dosynchronized, trocar por estruturas concorrentes ou particionar o lock.
Virtual threads (Loom): pinning e carrier starvation
Com virtual threads (Java 21+), dois problemas novos entram no radar. Pinning: até o JDK 23, uma virtual thread que bloqueia dentro desynchronized (ou em chamada nativa) fica presa à carrier thread, impedindo que a carrier sirva outras virtual threads — o JDK 24 resolveu o caso do synchronized, mas chamadas nativas ainda pinam. Carrier starvation: se todas as carriers estão pinadas ou ocupadas, milhares de virtual threads prontas simplesmente não rodam. Lembre que o formato clássico de dump não mostra virtual threads desmontadas — usejcmd Thread.dump_to_file -format=json para enxergá-las.
Análise manual vs analisador de thread dump
Análise manual vale a pena quando o dump é pequeno (dezenas de threads), o caso é pontual e você quer entender o problema em profundidade — é também a melhor forma de aprender. Com um editor de texto e o roteiro acima, um dump de aplicação simples se resolve em minutos.
A conta muda quando o dump tem centenas ou milhares de threads, quando você precisa comparar múltiplos dumps entre si, quando o incidente se repete e cada minuto de diagnóstico custa caro, ou quando o resultado precisa virar um relatório que outra equipe entenda. Agrupar 2.000 pilhas por similaridade na mão não é análise, é penitência — esse trabalho mecânico é exatamente o que uma ferramenta faz melhor.
Existem analisadores de thread dump online — incluindo o do ThreadMine, gratuito e sem cadastro para o primeiro dump. Se você quer aprofundar a leitura manual antes de automatizar, veja também nosso guia como analisar um thread dump Java, com um exemplo interpretado passo a passo.
Como o ThreadMine automatiza a análise
O ThreadMine aplica este guia inteiro em segundos. O upload aceita dumps de quatro formatos de JVM — HotSpot, OpenJ9, Zing e GraalVM — com detecção automática pelo cabeçalho, sem você precisar dizer qual é. O conteúdo é normalizado para um modelo único e passa por 15 detectores: deadlock declarado, contenção de monitor, pool exhaustion, thread leak, CPU spike, clusters de pilhas idênticas e outros padrões descritos acima — incluindo suporte a virtual threads, com detecção de pinning.
Cada problema vira um card com severidade, threads envolvidas e sugestão de correção, e o conjunto é resumido em um health score de A a F — o estado da JVM em uma letra, útil para abrir incidente sem anexar 4 MB de texto. Para os casos difíceis, a IA Vein analisa os problemas detectados em conjunto e aponta a provável causa raiz em linguagem natural.
O dump original continua disponível para leitura manual quando você quiser conferir — a ferramenta elimina o trabalho mecânico, não o seu julgamento.
Perguntas frequentes
O que é um thread dump?
Um thread dump é um snapshot textual de todas as threads de uma JVM em um instante: nome, estado, pilha de chamadas e locks detidos ou aguardados por cada uma. Ele serve para diagnosticar travamento, lentidão e consumo anormal de CPU sem reiniciar nem instrumentar a aplicação. A coleta é praticamente instantânea e segura para produção.
Como gerar um thread dump sem reiniciar a JVM?
Use jstack -l <pid> ou jcmd <pid> Thread.print no mesmo host da JVM — ambos imprimem o dump sem afetar o processo. Em Linux, kill -3 <pid> envia SIGQUIT e a JVM escreve o dump no próprio stdout, útil em containers sem JDK. Nenhuma dessas opções derruba ou pausa a aplicação de forma perceptível.
Quantos thread dumps devo coletar para diagnosticar um problema?
Pelo menos três, com cerca de 10 segundos de intervalo entre eles. Um dump isolado é uma foto: mostra threads paradas que podem estar apenas trabalhando. Comparando três dumps, uma thread com a mesma pilha em todos está travada — e não apenas lenta.
Qual a diferença entre BLOCKED e WAITING?
BLOCKED significa que a thread quer entrar em um bloco synchronized cujo monitor está detido por outra thread — é sempre contenção de lock. WAITING significa que a thread chamou wait(), park() ou join() sem timeout e espera ser acordada por outra. WAITING é normal em pools ociosos; BLOCKED em volume indica gargalo de sincronização.
Existe analisador de thread dump online grátis?
Sim. O ThreadMine analisa o primeiro thread dump gratuitamente e sem cadastro: você faz upload e recebe problemas detectados, health score e sugestões em segundos. Outros analisadores online também existem, com profundidades de análise variadas.
Um thread dump expõe dados sensíveis?
Ele contém nomes de classes, métodos, threads e endereços de locks — não valores de variáveis nem conteúdo de memória, ao contrário de um heap dump. Ainda assim, revela a estrutura interna do código e nomes de pacotes. Trate como artefato interno: evite colar em fóruns públicos e prefira ferramentas com política clara de retenção.
Quer ver tudo isso aplicado ao seu dump?
Faça upload e tenha health score, problemas detectados e sugestões em segundos. Sem cadastro para o primeiro dump.