The M:N Scheduling Model

Java implements virtual threads using an M:N model:

  • M (Millions): A large number of Virtual Threads.
  • N (Few): A small number of OS Carrier Threads (usually equal to CPU cores).

When a virtual thread runs code, it is "mounted" onto a carrier thread. When it performs a blocking I/O operation (like reading from a socket), the JVM "unmounts" it, freeing the carrier thread to execute another virtual thread. This non-blocking behavior is transparent to the developer.

Advertisement

Visualization: Carrier Scheduler

Carrier Scheduler Visualization (ForkJoinPool)

Ready Virtual Threads

VT #105
VT #106
VT #107
→ Mount →

Carrier Threads (OS)

Carrier-0
Running: VT #101
Carrier-1
Running: VT #104
Status: Carrier threads execute Virtual Threads from the shared work-stealing queue. When a VT blocks, it is unmounted, and the Carrier picks the next ready VT.
Advertisement

Why This Matters

This model solves the "Thread-per-Request" scalability bottleneck. In traditional server applications, the number of concurrent requests is limited by the number of OS threads (typically a few thousand). With virtual threads, the limit becomes available heap memory, allowing for significantly higher throughput for I/O-bound workloads.