Virtual threads no JDK 24: o que mudou para Java em produção
A JEP 491 elimina o pinning de carrier em blocos synchronized, mas o risco em produção se desloca para pools de conexão, ThreadLocal e recursos downstream.

Habilitar virtual threads em um serviço Spring Boot 3 é uma mudança de configuração de uma linha. O que acontece na semana seguinte, porém, depende de quanto código synchronized vive na sua stack de dependências e de como seus caches e pools foram dimensionados. Um artigo detalhado da InfoQ, escrito por Sandeep Bharadwaj, mapeia como o cenário de adoção mudou a partir do JDK 24.
O que a JEP 491 resolveu
O principal modo de falha no JDK 21 era o carrier-thread starvation causado por blocos synchronized. Segundo o artigo, uma virtual thread que entrava em um bloco sincronizado não conseguia desmontar do seu carrier mesmo ao bloquear em I/O. Com contenção suficiente, todos os carriers ficavam ocupados e o scheduler travava, com a JVM viva mas sem servir tráfego.
A JEP 491, Synchronize Virtual Threads without Pinning, entregue no JDK 24, reescreveu o rastreamento de posse de monitores. Agora virtual threads podem desmontar dentro de blocos synchronized, entrar em Object.wait() e readquirir monitores ao acordar, tudo sem prender o carrier. A InfoQ cita o caso documentado pelo time de JVM Ecosystem da Netflix ("Java↳Java42 conteúdosNovidades do Java 26 (para desenvolvedores)Dev (Back & Front) · mar 2026Visual Studio Code para Java: o guia completo (dicas, configuração e extensões)Dev (Back & Front) · out 2025Quarkus: Modernizando a linguagem Java para a era da nuvemDev (Back & Front) · nov 2020Ver tudo em Dev (Back & Front) → 21 Virtual Threads: Dude, Where's My Lock?") como o relato mais claro dessa classe de deadlock silencioso.
Pinning residual que ainda existe
A JEP 491 não elimina todo o pinning. De acordo com o artigo, ainda prendem o carrier:
- Frames de método nativo (JNI / FFM)
- Carga e inicialização de classes
- File I/O em disco local no Linux↳Linux34 conteúdosKali Linux em um Servidor VPS: como, quando e por que usar?DevSecOps · dez 2024Construindo um Windows Service ou Linux Daemon com Worker Service & .NET Core – Parte 2Dev (Back & Front) · jul 2020Criando uma WebApi utilizando .NET, Linux e VSCodeDev (Back & Front) · ago 2019Ver tudo em DevSecOps →, já que não há integração io_uring pronta para produção na JDK
A recomendação prática é configurar o JDK Flight Recorder (JFR) para emitir eventos jdk.VirtualThreadPinned e rodar a suíte de carga antes do rollout. Vale notar que a propriedade -Djdk.tracePinnedThreads, que existia no JDK 21, foi removida no JDK 24.
O risco se deslocou, não desapareceu
Uma vez que as virtual threads removem o teto de threads do servlet, o gargalo passa para o que está atrás: pools de conexão, rate limits, file descriptors e serviços downstream. Segundo o artigo, nenhuma atualização de JDK resolve isso sozinha; é uma decisão de design.
ThreadLocal: cache que para de cachear em silêncio
Esse é o ponto que exige auditoria de código, não configuração de observabilidade. A classe ThreadLocal foi pensada em 1998 para threads de plataforma longevas em pool. Virtual threads são efêmeras e não reutilizadas entre requisições, então cada nova thread ganha um ThreadLocalMap novo.
O padrão comum de usar ThreadLocal.withInitial() para alocar uma vez um objeto caro e não thread-safe (o exemplo clássico é SimpleDateFormat) passa a rodar a cada requisição. Não lança exceção, não emite aviso: só recomputa o valor toda vez. No benchmark público que acompanha o artigo, num teste de 60 segundos com 500 usuários simultâneos:
- Threads de plataforma: 200 inicializações do
ThreadLocal - Virtual threads: 443.267 inicializações
Uma razão de 2.216x, que apareceu primeiro como pressão inexplicável de GC. O mesmo cenário registrou throughput de 3.594 req/s (plataforma) contra 7.426 req/s (virtual), com p99 caindo de 147 ms para 53 ms, ou seja, o modo virtual aloca mais e ainda ganha, mas o rollout deve esperar mais atividade de GC.
O caminho: Scoped Values
O artigo aponta os Scoped Values, finalizados no JDK 25 via JEP 506, como o substituto correto para contexto de requisição da aplicação. Eles evitam surpresas de propagação do InheritableThreadLocal e se propagam automaticamente para tarefas filhas no StructuredTaskScope. Uma mudança de API na finalização: ScopedValue.orElse(null) não é mais permitido.
O que muda para quem constrói software no Brasil
Para times brasileiros escolhendo entre JDK 21 LTS e JDK 25 LTS em 2026, o JDK 25 é o primeiro LTS a incluir a JEP 491 e os Scoped Values finalizados. Na prática: Spring MVC com virtual threads é um bom padrão para serviços de I/O bloqueante, enquanto Spring WebFlux segue sendo a escolha para streaming, SSE, WebSockets e sistemas sensíveis a backpressure, que virtual threads não substituem. O código-fonte, dashboards e saída bruta do benchmark estão disponíveis no GitHub referenciado pelo artigo, para reproduzir os padrões em hardware próprio.
Fonte: InfoQ
Este artigo foi escrito por Redação iMasters. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.
Anthropic lança Opus 5.5 com tokens 20% mais baratos e nível do Fable em coding
O novo modelo troca o Opus 5 por preços menores e desempenho que supera o Fable, modelo maior da Anthropic, em vários benchmarks de código.











