SYSTEM Cited by 1 source
Java 21 Virtual Threads¶
Virtual threads (VTs) shipped as a preview in Java 19, stabilized in Java 21 via JEP 444, and are described as "lightweight threads that dramatically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications" (per the JDK docs).
Model¶
A VT is not 1:1 with an OS thread. It is "a task that is scheduled to a fork-join thread pool" — see concepts/fork-join-pool. The VT multiplexes onto a pool of underlying OS worker threads ("carrier threads") via JVM-managed continuations:
- When a VT does blocking I/O (or
LockSupport.park, or waits on aFuture/ReentrantLock/CompletableFuture), the runtime unmounts it from its carrier: the continuation saves the VT stack to the heap; the carrier is released back to the pool. - When the VT becomes runnable again, the scheduler mounts it on any available carrier and resumes execution from the saved continuation.
The default fork-join pool size is
Runtime.getRuntime().availableProcessors()
— typically equal to vCPU count on JVM-ergonomics-defaulted cloud
VMs.
Pinning¶
VTs cannot unmount in certain JVM states. The most important class for Java 21:
"A VT will be pinned to the underlying OS thread if it performs a blocking operation while inside a
synchronizedblock or method." — JDK 21 core docs
While pinned, the VT holds its carrier OS thread for the entire block — exactly the anti-behavior VTs were supposed to fix. concepts/virtual-thread-pinning is the canonical wiki entry for this failure mode.
Pinning also occurs inside native-code frames that hold
monitors, though the synchronized-block case is the most
common.
Removal of pinning-during-synchronized (in progress)¶
The Netflix 2024-07-29 post flags the JDK-level fix: "This is a
limitation in Java 21 and will be addressed in the future
releases." Subsequent JEPs (JEP 491,
targeted at a later JDK) describe removing the
pinning-during-synchronized constraint, letting VTs unmount
across synchronized the same way they do across
ReentrantLock. Until this lands and is adopted widely, the
operator-visible advice is: don't call blocking APIs inside
synchronized, and watch out for library authors who do.
Observability in Java 21¶
jstackdoes not show VT stacks — it only iterates platform threads.jcmd Thread.dump_to_filedoes include VTs (stack traces, IDs, states) but omits lock-owner/parking metadata in Java 21 — a named limitation being addressed.
See concepts/jcmd-thread-dump.
Tomcat + VT integration¶
Spring Boot's embedded Tomcat ships a
VirtualThreadExecutor
— a one-line config flag switches request handling from
platform-thread-per-request to VT-per-request. This preserves
Tomcat's blocking
per-request model while (in theory) removing the cap that the
platform thread-pool imposed.
Seen in¶
- sources/2024-07-29-netflix-java-21-virtual-threads-dude-wheres-my-lock
— Canonical wiki introduction. Production Netflix microservices
on Java 21 + Spring Boot 3 + embedded Tomcat hung with
closeWaitpile-up due to 4 VTs pinned insidesynchronizedon the Brave span-finish path, exhausting all 4 carrier threads on a 4-vCPU instance. The post also names the JDK limitations (missing lock metadata injcmddumps; pinning-during-synchronized) and the long-term fix path (upstream JDK changes). Canonical Java-language-runtime instance of patterns/upstream-the-fix.
Related¶
- concepts/virtual-thread — The primitive.
- concepts/virtual-thread-pinning — The failure mode.
- concepts/carrier-thread — The resource consumed by pinning.
- concepts/fork-join-pool — The scheduler substrate.
- concepts/jcmd-thread-dump — Diagnostic tool.
- patterns/blocking-model-per-request-tomcat — The shape VTs target.
- patterns/upstream-the-fix — JDK-level fix path for the pinning + observability limitations.
- systems/embedded-tomcat — Primary servlet-container consumer.
- systems/spring-boot — Framework enabling VT execution via single config.
- companies/netflix — Canonical adopter documented on the wiki.