How do virtual threads work? What are carrier threads, mo… — Cracked Java
// Concurrency & Multithreading · Virtual Threads & Structured Concurrency (Loom)
SeniorTheoryBig TechGoogle

How do virtual threads work? What are carrier threads, mounting, and unmounting?

The JVM multiplexes many virtual threads onto a few platform carrier threads. A virtual thread runs by mounting a carrier; when it blocks, it unmounts, copying its stack to the heap and freeing the carrier to run another virtual thread. This is M:N scheduling.

Carrier threads

Carriers are real platform threads in a dedicated ForkJoinPool, sized to the number of CPUs by default (jdk.virtualThreadScheduler.parallelism). A virtual thread can only execute while mounted on a carrier. Because carriers are few, the JVM must keep them busy — which is exactly what unmounting achieves.

Mounting and unmounting

A virtual thread is built on a continuation: a capturable, resumable execution state.

  • Mount: the scheduler picks a ready virtual thread, copies its stack frames onto a carrier's stack, and runs it.
  • Unmount: when the virtual thread hits a blocking point — socket.read(), Thread.sleep(), a j.u.c lock, BlockingQueue.take() — the JDK's instrumented blocking call suspends the continuation, copies the live stack frames to the heap, and releases the carrier. The carrier immediately runs another virtual thread.

When the blocking operation completes (e.g. the OS reports the socket is readable), the virtual thread becomes runnable again, is re-mounted onto some carrier, its frames restored, and execution resumes right after the blocking call.

void handle(Socket s) throws IOException {
    var in = s.getInputStream();
    byte[] buf = new byte[1024];
    int n = in.read(buf);   // unmounts here; carrier is freed
    process(buf, n);        // resumes on a (possibly different) carrier
}
runnable --mount--> RUNNING on carrier
 ^                      |
 |                  blocking call
 |                      v
 +--I/O ready--  UNMOUNTED (stack on heap, carrier freed)
Lifecycle of one virtual thread

Why it scales

While a virtual thread is unmounted, it costs only heap for its parked stack — no OS thread, no carrier. So 10,000 requests waiting on a database can be 10,000 cheap parked stacks served by a handful of carriers. The blocking calls in the JDK were rewritten to cooperate with this; that is why ordinary blocking code "just works."

Mark your status