Você abre um thread dump, percorre cem linhas, e cada thread carrega um estado: RUNNABLE, BLOCKED, WAITING, TIMED_WAITING. O instinto é tratar o estado como o diagnóstico — "tem muito WAITING, o app travou". Esse instinto erra com frequência suficiente para te custar um incidente. O estado é uma pista sobre como a thread está parada, não sobre se algo de fato quebrou. Este guia percorre cada estado, mostra a linha de dump que o produz e — a parte que importa — como distinguir uma thread ociosa perfeitamente saudável de uma genuinamente travada.
Os seis estados de thread do Java
Toda thread viva reporta um valor do enum java.lang.Thread.State. São exatamente seis, e só três deles aparecem de forma interessante num dump real:
- NEW — criada com
new Thread(), masstart()nunca foi chamado. Você quase nunca vê isso num dump; a thread ainda nem foi escalonada. - RUNNABLE — executando na JVM, ou pronta para rodar aguardando apenas um core de CPU (ou o SO retornar de uma chamada nativa/IO). Veja a armadilha na próxima seção.
- BLOCKED — esperando adquirir o monitor de um objeto para entrar ou reentrar num bloco/método
synchronized. É sempre sobre um monitor; é sempre sinal de contenção. - WAITING — esperando indefinidamente por outra thread agir:
Object.wait()sem timeout,Thread.join()sem timeout, ouLockSupport.park(). - TIMED_WAITING — igual ao WAITING, mas com prazo:
Thread.sleep(n),Object.wait(timeout),Thread.join(timeout),LockSupport.parkNanos()/parkUntil(). - TERMINATED —
run()retornou. A thread está morta; sumiu da maioria dos dumps.
RUNNABLE não quer dizer executando
O erro de leitura mais comum não é sobre os três estados -WAITING — é sobre o RUNNABLE. Uma thread bloqueada dentro de um socket read nativo reporta RUNNABLE, porque, para a JVM, ela está rodando código nativo; quem espera é o SO, não a JVM. Então um dump cheio de threads RUNNABLE paradas em socketRead0 não é CPU ocupada — é um monte de threads bloqueadas num downstream lento, e sua CPU pode estar ociosa. Leia o frame do topo da pilha antes de confiar na palavra RUNNABLE.
BLOCKED — contenção de monitor
BLOCKED tem exatamente um significado: a thread quer entrar num bloco/método synchronized e outra thread detém o monitor daquele objeto no momento. O dump deixa explícito com waiting to lock <0x…> num monitor que alguma outra thread reporta como locked <0x…>.
Uma thread BLOCKED é normal — locks existem para serem disputados. O sinal a perseguir é muitas threads BLOCKED no mesmo endereço de monitor. Isso é um ponto quente de contenção: uma única seção synchronized que virou gargalo, ou um lock segurado enquanto se faz trabalho lento (IO dentro da seção crítica). Se essas threads nunca progridem e quem detém o lock está, ele próprio, esperando por uma delas, você pode estar diante de um deadlock, e não de mera contenção — a JVM diz explicitamente se for o caso.
WAITING — ocioso, ou travado em silêncio
WAITING é o estado que mais gente superinterpreta. Ele significa que a thread se estacionou sem timeout e não vai acordar até que outra thread a sinalize explicitamente. E isso descreve, ao mesmo tempo, os dois casos abaixo:
- Um worker ocioso e saudável. Um worker de pool chamando
LinkedBlockingQueue.take()estaciona em WAITING até uma tarefa chegar. Um dump tirado num servidor ocioso mostra dezenas deles. Nada de errado. - Uma thread genuinamente travada. Uma thread em
Object.wait()por uma notificação que já foi enviada (bug de notify perdido), ou uma perna de um deadlock baseado emReentrantLock, fica no mesmíssimo estado WAITING — para sempre.
O estado é idêntico; só a pilha os separa. WAITING no take() de uma fila é ocioso-saudável. WAITING para adquirir um lock da aplicação, ou numa condição que ninguém sinaliza, é problema. Você não decide pela palavra do estado — precisa ler o frame logo abaixo dela.
TIMED_WAITING — quase sempre benigno
TIMED_WAITING é WAITING com despertador: a thread acorda sozinha quando o timeout expira, mesmo que ninguém a sinalize. É o estado menos alarmante — um scheduler em Thread.sleep(), um poller fazendo queue.poll(500, MILLISECONDS), um pool de conexões em parkNanos aguardando um lease. Como se recupera sozinho, TIMED_WAITING quase nunca dá deadlock.
A única coisa que vale checar: uma thread que está sempre em TIMED_WAITING com um timeout muito longo em todo dump que você tira está, na prática, ociosa naquela janela — ok se for um poller, suspeito se ela deveria estar servindo uma requisição. Colete vários dumps com alguns segundos de intervalo; uma espera temporizada benigna se move, uma travada não.
Ocioso vs travado: o estado sozinho mente
Junte os três e a regra é simples: o estado te diz o mecanismo, a pilha e o lock te dizem o significado.
- Leia o frame do topo.
queue.take()/Unsafe.parksob um worker de pool = ocioso. Um método da aplicação sob um monitor = trabalho em andamento ou contenção. - Leia as linhas de lock.
waiting to lock <0x…>mais um detentor que nunca libera é a assinatura de travado.parking to wait fora condição do próprio pool é a assinatura de ocioso. - Compare dumps ao longo do tempo. Tire dois ou três com alguns segundos de intervalo. Threads que mudam de estado ou de pilha estão vivas; threads congeladas no mesmíssimo frame e lock em todos os dumps são as realmente travadas.
Uma linha de dump por estado
BLOCKED (on object monitor)
"http-nio-8080-exec-7" #41 daemon prio=5 tid=0x... nid=0x... waiting for monitor entry [0x...] java.lang.Thread.State: BLOCKED (on object monitor) at com.example.Cache.get(Cache.java:42) - waiting to lock <0x000000076ab00000> (a com.example.Cache) at com.example.Api.handle(Api.java:88)
O waiting to lock <0x…ab00000> é o indício. Alguma outra thread no mesmo dump detém exatamente esse monitor. Ache-a — esse detentor é o seu gargalo.
WAITING (parking) — um worker de pool ocioso e saudável
"pool-2-thread-1" #22 prio=5 tid=0x... nid=0x... waiting on condition [0x...] java.lang.Thread.State: WAITING (parking) at jdk.internal.misc.Unsafe.park(Native Method) - parking to wait for <0x000000076ab10000> (a j.u.c.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:435)
WAITING, mas o frame de baixo é LinkedBlockingQueue.take() — esse worker está ocioso, esperando trabalho. Vinte desses num servidor tranquilo é exatamente o que você quer ver, não um problema.
TIMED_WAITING (sleeping)
"scheduler-1" #18 prio=5 tid=0x... nid=0x... waiting on condition [0x...] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at com.example.Scheduler.loop(Scheduler.java:31)
Recupera-se sozinho: esta thread acorda por conta própria quando o sleep expira. Benigno, a menos que o intervalo de sleep esteja errado para o que a thread deveria estar fazendo.
Como o ThreadMine classifica isso automaticamente
O ThreadMine não só imprime o estado ao lado de cada thread — ele faz a leitura que você faria na mão. Agrupa as threads por estado e, para cada thread BLOCKED, resolve o endereço do monitor para a thread que o detém, de modo que um ponto quente de contenção (muitas threads esperando num lock) apareça como um único achado ranqueado, em vez de uma parede de linhas.
Para threads WAITING e TIMED_WAITING ele lê a pilha, não só o estado: um worker de pool estacionado em queue.take() é rotulado como ocioso e sai do seu caminho, enquanto uma thread esperando um lock da aplicação, uma condição que nunca é sinalizada, ou um ciclo de locks é sinalizada como suspeita. Essa é a diferença entre "80 threads WAITING" (que te dá pânico à toa) e "3 threads travadas no mesmo lock, 77 workers ociosos" (que aponta o bug de verdade). Leve um dump e o ThreadMine te diz em segundos quais das suas threads paradas estão saudáveis e quais estão travadas.
Conclusão
Estados de thread são um vocabulário, não um diagnóstico. BLOCKED é o que se deve levar a sério por padrão — sempre significa contenção de monitor. WAITING e TIMED_WAITING são ambíguos por natureza: na maior parte do tempo são threads ociosas fazendo exatamente o que deveriam, e de vez em quando são a prova do crime. A palavra nunca decide — a pilha e o lock decidem. Leia esses, compare alguns dumps ao longo do tempo, e deixe a ferramenta agrupar o ruído para que a única thread travada não fique escondida entre oitenta saudáveis.
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.