Inside Java Virtual Threads: A Deep Dive into the JVM Implementation. Part 1
Virtual threads make concurrency simple again — giving you the scalability of asynchronous programming with the simplicity of plain…
Inside Java Virtual Threads: A Deep Dive into the JVM Implementation. Part 1
Virtual threads make concurrency simple again — giving you the scalability of asynchronous programming with the simplicity of plain, readable, synchronous code.
Let’s start with a simple example 👇
// Launch two tasks in parallel, but the code looks synchronous
Future<String> serviceA = executor.submit(() -> fetch("Service A"));
Future<String> serviceB = executor.submit(() -> fetch("Service B"));
// Wait for both results — simple blocking style
String result = serviceA.get() + " + " + serviceB.get();
❓ Question 1
Is this a good way to write concurrent code in Java?
💬 Answer
It depends — on whether you’re using virtual threads or not.
- 🚫 If you use platform threads (e.g. Executors.newFixedThreadPool()): this blocking style doesn’t scale well. Each thread blocks a real OS thread and consumes significant memory. As the number of concurrent tasks grows, so do resource costs.
- ✅ If you use virtual threads (Executors.newVirtualThreadPerTaskExecutor()): this exact same code becomes lightweight and highly scalable.The JVM can park virtual threads when they block, freeing real threads to handle more work — achieving asynchronous scalability without changing your programming style.
🧠 The Big Idea
Virtual threads let you write simple, synchronous code that behaves like efficient asynchronous code under the hood.
You get the best of both worlds — readability and performance, simplicity and scalability.
⚙️ So… How Does It Actually Work?
When a virtual thread blocks (for example, during future.get() ), the JDK decides how to “park” it depending on whether it’s virtual or platform-based:
if (t.isVirtual()) {
VirtualThreads.park();
} else {
U.park(false, 0L);
}
This simple-looking conditional hides a lot of JVM magic.
When a virtual thread is parked, it doesn’t actually block a real OS thread.
Instead, the JVM suspends its continuation (essentially a snapshot of its stack) and frees the carrier thread to run something else.
🔍 Under the Hood: The park() Method
From the JDK source code:
/**
* Parks until unparked or interrupted. If already unparked then the parking
* permit is consumed and this method completes immediately (meaning it doesn't
* yield). It also completes immediately if the interrupt status is set.
*/
@Override
void park() {
assert Thread.currentThread() == this;
// complete immediately if parking permit available or interrupted
if (getAndSetParkPermit(false) || interrupted)
return;
// park the thread
boolean yielded = false;
setState(PARKING);
try {
yielded = yieldContinuation(); // may throw
} finally {
assert (Thread.currentThread() == this) && (yielded == (state() == RUNNING));
if (!yielded) {
assert state() == PARKING;
setState(RUNNING);
}
}
// park on the carrier thread when pinned
if (!yielded) {
parkOnCarrierThread(false, 0);
}
}
The Key Operation — yieldContinuation()
/**
* Unmounts this virtual thread, invokes Continuation.yield, and re-mounts the
* thread when continued. When enabled, JVMTI must be notified from this method.
* @return true if the yield was successful
*/
@Hidden
@ChangesCurrentThread
private boolean yieldContinuation() {
// unmount
notifyJvmtiUnmount(/*hide*/true);
unmount();
try {
return Continuation.yield(VTHREAD_SCOPE);
} finally {
// re-mount
mount();
notifyJvmtiMount(/*hide*/false);
}
}
Let’s translate that into plain English 👇
- Unmount the virtual thread from its carrier (real OS thread).
- Save its current stack into a heap-allocated continuation (pause point).
- Free the carrier thread to do other work.
- When the virtual thread is unparked, remount it and continue from the exact point where it paused.
⚡ How Java Virtual Threads Handle I/O Blocking
Let’s revisit our question — but this time for I/O.
❓ Question 2
Is this code still “good” when it involves blocking I/O operations like SocketChannel.read()?
💬 The Answer
Again, it depends on the thread type.
When a virtual thread performs a blocking I/O call — for example, SocketChannel.read() — it does not block a real OS thread.
Instead:
1️⃣ The JVM detects the blocking I/O call.
2️⃣ The virtual thread unmounts from its carrier thread.
3️⃣ Its Java stack is saved in a continuation.
4️⃣ The carrier thread is freed for other work.
5️⃣ When the I/O operation completes, the virtual thread resumes from the exact point it paused.
This required deep integration in the JDK, especially in classes like SocketChannelImpl, to make traditional blocking I/O work seamlessly with virtual threads.
Internals: How the JDK Does It:
// Read, no timeout
configureSocketNonBlockingIfVirtualThread();
n = tryRead(b, off, len);
while (IOStatus.okayToRetry(n) && isOpen()) {
park(Net.POLLIN);
n = tryRead(b, off, len);
}
Here’s what happens inside the park() implementation:
default void park(int event, long nanos) throws IOException {
if (Thread.currentThread().isVirtual()) {
Poller.poll(getFDVal(), event, nanos, this::isOpen);
} else {
long millis;
if (nanos <= 0) {
millis = -1;
} else {
millis = NANOSECONDS.toMillis(nanos);
if (nanos > MILLISECONDS.toNanos(millis)) {
// Round up any excess nanos to the nearest millisecond to
// avoid parking for less than requested.
millis++;
}
}
Net.poll(getFD(), event, millis);
}
}
Notice how virtual threads take a different code path — their blocking is managed cooperatively via the poller rather than by locking a native thread.
🧵 Conclusion
Java Virtual Threads represent one of the most significant evolutions in JVM history.
They bring lightweight concurrency to the platform while preserving Java’s familiar synchronous style.
Key takeaways:
- Elegant mount/unmount mechanism for switching thread context.
- Continuation-based system for saving and restoring execution state.
- Smart blocking operation detection in the JDK.
- A dedicated scheduler optimized for virtual workloads.
This isn’t just a new API — it’s a rethinking of concurrency at the JVM level.
Understanding how it works helps developers appreciate the sophisticated machinery making our concurrent Java code both simpler and faster.
🧩 Coming Up in Part 2:
We’ll explore how continuations are implemented, and how you can tune your applications to make the most of this groundbreaking feature.
메타데이터
- post_id
- 0cf6793ac3bd
- slug
- inside-java-virtual-threads-a-deep-dive-into-the-jvm-implementation-part-1-0cf6793ac3bd
- url
- https://medium.com/@nikolaykudinov/inside-java-virtual-threads-a-deep-dive-into-the-jvm-implementation-part-1-0cf6793ac3bd
- canonical_url
- https://medium.com/@nikolaykudinov/inside-java-virtual-threads-a-deep-dive-into-the-jvm-implementation-part-1-0cf6793ac3bd
- author_url
- https://medium.com/@nikolaykudinov
- status
- ok
- fetched_at
- 2026-06-25 07:00:49